diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fb6857a..f8e0ace 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "me.lecaro.breakout" minSdk = 21 targetSdk = 34 - versionCode = 29035871 - versionName = "29035871" + versionCode = 29036807 + versionName = "29036807" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 20945b0..f2a54af 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + Breakout 71 \ No newline at end of file +Breakout 71 \ No newline at end of file diff --git a/app/src/main/java/me/lecaro/breakout/MainActivity.kt b/app/src/main/java/me/lecaro/breakout/MainActivity.kt index b66b748..7baecf5 100644 --- a/app/src/main/java/me/lecaro/breakout/MainActivity.kt +++ b/app/src/main/java/me/lecaro/breakout/MainActivity.kt @@ -1,13 +1,40 @@ package me.lecaro.breakout +import android.app.Activity +import android.app.DownloadManager +import android.content.Context +import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.os.Environment import android.util.Log import android.view.Window import android.view.WindowManager import android.webkit.ConsoleMessage +import android.webkit.DownloadListener +import android.webkit.ValueCallback import android.webkit.WebChromeClient import android.webkit.WebView +import android.widget.Toast +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +const val CHOOSE_FILE_REQUEST_CODE = 548459 class MainActivity : android.app.Activity() { + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + CHOOSE_FILE_REQUEST_CODE -> { + if (resultCode == RESULT_OK) { + filePathCallback?.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data)) + filePathCallback = null + } + } + } + } + var filePathCallback: ValueCallback>? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -15,11 +42,13 @@ class MainActivity : android.app.Activity() { WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ); -// WebView.setWebContentsDebuggingEnabled(true) val webView = WebView(this) webView.settings.javaScriptEnabled = true webView.settings.domStorageEnabled = true + webView.settings.setSupportZoom(false) webView.loadUrl("file:///android_asset/index.html?isInWebView=true") + + webView.webChromeClient = object : WebChromeClient() { override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { Log.d( @@ -28,7 +57,46 @@ class MainActivity : android.app.Activity() { ) return true } + override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback>?, fileChooserParams: FileChooserParams?): Boolean { + startActivityForResult(fileChooserParams?.createIntent(), CHOOSE_FILE_REQUEST_CODE) + this@MainActivity.filePathCallback = filePathCallback + return true + } } + webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> + + if (!url.startsWith("data:")) { + Log.w("DL","url ignored because it does not start with data:") + return@DownloadListener + } + val sdf = SimpleDateFormat("yyyy-M-dd-hh-mm") + val currentDate = sdf.format(Date()) + // Extract filename from contentDisposition if available + + if (url.startsWith("data:application/json;base64,")) { + Log.d("DL","saving application/json ") + val base64Data = url.substringAfterLast(',') + val decodedBytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT) + val jsonData = String(decodedBytes) + ; + val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + val fileName = "breakout-71-save-$currentDate.b71" + val file = File(dir, fileName) + file.writeText(jsonData) + Toast.makeText(this, "Saved in $dir", Toast. LENGTH_LONG).show() + Log.d("DL","finished saving application/json ") + + }else if (url.startsWith("data:video/webm;base64,")){ + Log.d("DL","saving video/webm ") + // TODO + Log.d("DL","finished savign video/webm ") + }else{ + Log.w("DL","unexpected type "+url) + } + + }) + + setContentView(webView) } diff --git a/dist/PWA/sw-b71.js b/dist/PWA/sw-b71.js index 9aac844..f32287c 100644 --- a/dist/PWA/sw-b71.js +++ b/dist/PWA/sw-b71.js @@ -1,2 +1,2 @@ -function e(e,t,n,r,a,i,c){try{var o=e[i](c),u=o.value}catch(e){n(e);return}o.done?t(u):Promise.resolve(u).then(r,a)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(a,i){var c=t.apply(n,r);function o(t){e(c,a,i,o,u,"next",t)}function u(t){e(c,a,i,o,u,"throw",t)}o(void 0)})}}function n(e,t){var n,r,a,i,c={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return i={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function o(i){return function(o){return function(i){if(n)throw TypeError("Generator is already executing.");for(;c;)try{if(n=1,r&&(a=2&i[0]?r.return:i[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,i[1])).done)return a;switch(r=0,a&&(i=[2&i[0],a.value]),i[0]){case 0:case 1:a=i;break;case 4:return c.label++,{value:i[1],done:!1};case 5:c.label++,r=i[1],i=[0];continue;case 7:i=c.ops.pop(),c.trys.pop();continue;default:if(!(a=(a=c.trys).length>0&&a[a.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]0&&a[a.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]","src/PWA/sw-b71.js"],"sourcesContent":["// The version of the cache.\nfunction asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {\n try {\n var info = gen[key](arg);\n var value = info.value;\n } catch (error) {\n reject(error);\n return;\n }\n if (info.done) {\n resolve(value);\n } else {\n Promise.resolve(value).then(_next, _throw);\n }\n}\nfunction _async_to_generator(fn) {\n return function() {\n var self1 = this, args = arguments;\n return new Promise(function(resolve, reject) {\n var gen = fn.apply(self1, args);\n function _next(value) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value);\n }\n function _throw(err) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err);\n }\n _next(undefined);\n });\n };\n}\nfunction _ts_generator(thisArg, body) {\n var f, y, t, g, _ = {\n label: 0,\n sent: function() {\n if (t[0] & 1) throw t[1];\n return t[1];\n },\n trys: [],\n ops: []\n };\n return g = {\n next: verb(0),\n \"throw\": verb(1),\n \"return\": verb(2)\n }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() {\n return this;\n }), g;\n function verb(n) {\n return function(v) {\n return step([\n n,\n v\n ]);\n };\n }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while(_)try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [\n op[0] & 2,\n t.value\n ];\n switch(op[0]){\n case 0:\n case 1:\n t = op;\n break;\n case 4:\n _.label++;\n return {\n value: op[1],\n done: false\n };\n case 5:\n _.label++;\n y = op[1];\n op = [\n 0\n ];\n continue;\n case 7:\n op = _.ops.pop();\n _.trys.pop();\n continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {\n _ = 0;\n continue;\n }\n if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {\n _.label = op[1];\n break;\n }\n if (op[0] === 6 && _.label < t[1]) {\n _.label = t[1];\n t = op;\n break;\n }\n if (t && _.label < t[2]) {\n _.label = t[2];\n _.ops.push(op);\n break;\n }\n if (t[2]) _.ops.pop();\n _.trys.pop();\n continue;\n }\n op = body.call(thisArg, _);\n } catch (e) {\n op = [\n 6,\n e\n ];\n y = 0;\n } finally{\n f = t = 0;\n }\n if (op[0] & 5) throw op[1];\n return {\n value: op[0] ? op[1] : void 0,\n done: true\n };\n }\n}\nvar VERSION = \"29035871\";\n// The name of the cache\nvar CACHE_NAME = \"breakout-71-\".concat(VERSION);\n// The static resources that the app needs to function.\nvar APP_STATIC_RESOURCES = [\n \"/\"\n];\n// On install, cache the static resources\nself.addEventListener(\"install\", function(event) {\n event.waitUntil(_async_to_generator(function() {\n var cache;\n return _ts_generator(this, function(_state) {\n switch(_state.label){\n case 0:\n return [\n 4,\n caches.open(CACHE_NAME)\n ];\n case 1:\n cache = _state.sent();\n cache.addAll(APP_STATIC_RESOURCES);\n return [\n 2\n ];\n }\n });\n })());\n});\n// delete old caches on activate\nself.addEventListener(\"activate\", function(event) {\n event.waitUntil(_async_to_generator(function() {\n var names;\n return _ts_generator(this, function(_state) {\n switch(_state.label){\n case 0:\n return [\n 4,\n caches.keys()\n ];\n case 1:\n names = _state.sent();\n return [\n 4,\n Promise.all(names.map(function(name) {\n if (name !== CACHE_NAME) return caches[\"delete\"](name);\n }))\n ];\n case 2:\n _state.sent();\n return [\n 4,\n clients.claim()\n ];\n case 3:\n _state.sent();\n return [\n 2\n ];\n }\n });\n })());\n});\nself.addEventListener(\"fetch\", function(event) {\n if (event.request.mode === \"navigate\" && event.request.url.endsWith(\"/index.html?isPWA=true\")) {\n event.respondWith(caches.match(\"/\"));\n return;\n }\n});\n\n//# sourceMappingURL=sw-b71.js.map\n","// The version of the cache.\nconst VERSION = \"29035871\";\n\n// The name of the cache\nconst CACHE_NAME = `breakout-71-${VERSION}`;\n\n// The static resources that the app needs to function.\nconst APP_STATIC_RESOURCES = [\"/\"];\n\n// On install, cache the static resources\nself.addEventListener(\"install\", (event) => {\n event.waitUntil(\n (async () => {\n const cache = await caches.open(CACHE_NAME);\n cache.addAll(APP_STATIC_RESOURCES);\n })(),\n );\n});\n\n// delete old caches on activate\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(\n (async () => {\n const names = await caches.keys();\n await Promise.all(\n names.map((name) => {\n if (name !== CACHE_NAME) {\n return caches.delete(name);\n }\n }),\n );\n await clients.claim();\n })(),\n );\n});\n\nself.addEventListener(\"fetch\", (event) => {\n if (\n event.request.mode === \"navigate\" &&\n event.request.url.endsWith(\"/index.html?isPWA=true\")\n ) {\n event.respondWith(caches.match(\"/\"));\n return;\n }\n});\n"],"names":["asyncGeneratorStep","gen","resolve","reject","_next","_throw","key","arg","info","value","error","done","Promise","then","_async_to_generator","fn","self1","args","arguments","apply","err","undefined","_ts_generator","thisArg","body","f","y","t","g","_","label","sent","trys","ops","next","verb","Symbol","iterator","n","v","step","op","TypeError","call","pop","length","push","e","CACHE_NAME","concat","APP_STATIC_RESOURCES","self","addEventListener","event","waitUntil","_state","caches","open","cache","addAll","keys","all","names","map","name","clients","claim","request","mode","url","endsWith","respondWith","match"],"version":3,"file":"sw-b71.js.map"} \ No newline at end of file +{"mappings":"A,S,E,C,C,C,C,C,C,C,C,C,C,C,C,C,E,G,C,I,E,C,C,E,C,G,E,E,K,A,C,M,E,C,E,G,M,C,E,I,C,E,G,Q,O,C,G,I,C,E,E,C,S,E,C,E,O,W,I,E,I,C,E,U,O,I,Q,S,C,C,C,E,I,E,E,K,C,E,G,S,E,C,E,E,E,E,E,E,E,O,E,C,S,E,C,E,E,E,E,E,E,E,Q,E,C,E,K,E,E,C,C,S,E,C,C,C,E,I,E,E,E,E,E,C,M,E,K,W,G,A,E,C,C,E,C,M,C,C,E,C,O,C,C,E,A,E,K,E,C,I,E,A,E,O,E,C,K,E,G,M,E,G,O,E,E,E,A,Y,O,Q,C,C,C,O,Q,C,C,W,O,I,A,C,E,E,S,E,C,E,O,S,C,E,O,A,S,C,E,G,E,M,A,U,mC,K,G,G,C,G,E,E,G,C,E,A,E,C,C,E,C,E,M,C,C,C,E,C,E,K,E,C,A,C,E,E,M,A,G,E,I,C,G,C,E,E,I,A,G,C,A,C,E,E,I,C,E,C,C,E,C,E,I,C,O,E,O,E,E,A,G,C,E,C,A,E,C,C,E,C,E,K,C,A,E,C,C,E,E,K,E,K,E,E,E,K,M,E,O,E,K,G,C,M,C,C,E,C,K,C,C,C,M,E,E,K,G,E,C,C,E,C,E,C,E,C,Q,M,E,E,E,G,C,G,G,E,I,C,G,G,Q,S,G,C,C,E,A,C,E,E,I,A,E,M,C,G,C,C,E,M,C,E,A,G,C,A,I,C,C,E,E,A,I,C,C,E,A,E,C,E,E,Q,C,G,A,I,C,C,E,E,C,C,G,C,C,E,C,C,C,E,E,C,C,E,C,C,C,E,A,E,C,E,K,C,C,C,E,C,K,C,G,A,I,C,C,E,E,E,K,C,C,C,E,C,C,E,K,C,C,C,E,C,E,E,K,C,G,G,E,K,C,C,C,E,C,C,E,K,C,C,C,E,C,E,G,C,I,C,G,K,C,C,C,E,E,E,G,C,G,G,E,I,C,G,G,Q,C,E,E,I,C,E,E,C,M,E,C,E,C,E,E,C,E,C,Q,C,E,E,C,C,G,A,E,C,C,E,C,M,C,C,E,C,M,C,M,C,C,E,C,C,C,E,C,K,E,K,C,C,C,E,C,E,E,C,C,C,CCIA,IAAM,EAAc,eAAsB,MAAA,CAH1B,YAMV,EAAuB,CAAC,IAAI,CAGlC,KAAK,gBAAgB,CAAC,UAAW,SAAC,CAAlC,EACE,EAAM,SAAS,CACb,EAAC,W,O,E,I,C,S,C,E,O,E,K,E,K,EACe,MAAA,C,EAAM,OAAO,IAAI,CAAC,G,A,M,E,OAChC,AADc,EAAR,IAAA,GACA,MAAM,CAAC,G,C,E,A,C,EACf,KAEJ,GAGA,KAAK,gBAAgB,CAAC,WAAY,SAAC,CAAnC,EACE,EAAM,SAAS,CACb,EAAC,W,O,E,I,C,S,C,E,O,E,K,E,K,EACe,MAAA,C,EAAM,OAAO,IAAI,G,A,M,EAC/B,MAAA,C,EAAM,QAAQ,GAAG,CACf,AAFY,EAAR,IAAA,GAEE,GAAG,CAAC,SAAC,CADP,EAEF,GAAI,IAAS,EACX,OAAO,OAAO,MAAM,CAAC,EAEzB,I,A,M,EAEF,OAPA,EAAA,IAAA,GAOA,C,EAAM,QAAQ,KAAK,G,A,M,E,OAAnB,EAAA,IAAA,G,C,E,A,C,EACF,KAEJ,GAEA,KAAK,gBAAgB,CAAC,QAAS,SAAC,CAAhC,EACE,GACE,AAAuB,aAAvB,EAAM,OAAO,CAAC,IAAI,EAClB,EAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,0BAC3B,CACA,EAAM,WAAW,CAAC,OAAO,KAAK,CAAC,MAC/B,MACF,CACF","sources":["","src/PWA/sw-b71.js"],"sourcesContent":["// The version of the cache.\nfunction asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {\n try {\n var info = gen[key](arg);\n var value = info.value;\n } catch (error) {\n reject(error);\n return;\n }\n if (info.done) {\n resolve(value);\n } else {\n Promise.resolve(value).then(_next, _throw);\n }\n}\nfunction _async_to_generator(fn) {\n return function() {\n var self1 = this, args = arguments;\n return new Promise(function(resolve, reject) {\n var gen = fn.apply(self1, args);\n function _next(value) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value);\n }\n function _throw(err) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err);\n }\n _next(undefined);\n });\n };\n}\nfunction _ts_generator(thisArg, body) {\n var f, y, t, g, _ = {\n label: 0,\n sent: function() {\n if (t[0] & 1) throw t[1];\n return t[1];\n },\n trys: [],\n ops: []\n };\n return g = {\n next: verb(0),\n \"throw\": verb(1),\n \"return\": verb(2)\n }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() {\n return this;\n }), g;\n function verb(n) {\n return function(v) {\n return step([\n n,\n v\n ]);\n };\n }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while(_)try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [\n op[0] & 2,\n t.value\n ];\n switch(op[0]){\n case 0:\n case 1:\n t = op;\n break;\n case 4:\n _.label++;\n return {\n value: op[1],\n done: false\n };\n case 5:\n _.label++;\n y = op[1];\n op = [\n 0\n ];\n continue;\n case 7:\n op = _.ops.pop();\n _.trys.pop();\n continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {\n _ = 0;\n continue;\n }\n if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {\n _.label = op[1];\n break;\n }\n if (op[0] === 6 && _.label < t[1]) {\n _.label = t[1];\n t = op;\n break;\n }\n if (t && _.label < t[2]) {\n _.label = t[2];\n _.ops.push(op);\n break;\n }\n if (t[2]) _.ops.pop();\n _.trys.pop();\n continue;\n }\n op = body.call(thisArg, _);\n } catch (e) {\n op = [\n 6,\n e\n ];\n y = 0;\n } finally{\n f = t = 0;\n }\n if (op[0] & 5) throw op[1];\n return {\n value: op[0] ? op[1] : void 0,\n done: true\n };\n }\n}\nvar VERSION = \"29036807\";\n// The name of the cache\nvar CACHE_NAME = \"breakout-71-\".concat(VERSION);\n// The static resources that the app needs to function.\nvar APP_STATIC_RESOURCES = [\n \"/\"\n];\n// On install, cache the static resources\nself.addEventListener(\"install\", function(event) {\n event.waitUntil(_async_to_generator(function() {\n var cache;\n return _ts_generator(this, function(_state) {\n switch(_state.label){\n case 0:\n return [\n 4,\n caches.open(CACHE_NAME)\n ];\n case 1:\n cache = _state.sent();\n cache.addAll(APP_STATIC_RESOURCES);\n return [\n 2\n ];\n }\n });\n })());\n});\n// delete old caches on activate\nself.addEventListener(\"activate\", function(event) {\n event.waitUntil(_async_to_generator(function() {\n var names;\n return _ts_generator(this, function(_state) {\n switch(_state.label){\n case 0:\n return [\n 4,\n caches.keys()\n ];\n case 1:\n names = _state.sent();\n return [\n 4,\n Promise.all(names.map(function(name) {\n if (name !== CACHE_NAME) return caches[\"delete\"](name);\n }))\n ];\n case 2:\n _state.sent();\n return [\n 4,\n clients.claim()\n ];\n case 3:\n _state.sent();\n return [\n 2\n ];\n }\n });\n })());\n});\nself.addEventListener(\"fetch\", function(event) {\n if (event.request.mode === \"navigate\" && event.request.url.endsWith(\"/index.html?isPWA=true\")) {\n event.respondWith(caches.match(\"/\"));\n return;\n }\n});\n\n//# sourceMappingURL=sw-b71.js.map\n","// The version of the cache.\nconst VERSION = \"29036807\";\n\n// The name of the cache\nconst CACHE_NAME = `breakout-71-${VERSION}`;\n\n// The static resources that the app needs to function.\nconst APP_STATIC_RESOURCES = [\"/\"];\n\n// On install, cache the static resources\nself.addEventListener(\"install\", (event) => {\n event.waitUntil(\n (async () => {\n const cache = await caches.open(CACHE_NAME);\n cache.addAll(APP_STATIC_RESOURCES);\n })(),\n );\n});\n\n// delete old caches on activate\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(\n (async () => {\n const names = await caches.keys();\n await Promise.all(\n names.map((name) => {\n if (name !== CACHE_NAME) {\n return caches.delete(name);\n }\n }),\n );\n await clients.claim();\n })(),\n );\n});\n\nself.addEventListener(\"fetch\", (event) => {\n if (\n event.request.mode === \"navigate\" &&\n event.request.url.endsWith(\"/index.html?isPWA=true\")\n ) {\n event.respondWith(caches.match(\"/\"));\n return;\n }\n});\n"],"names":["asyncGeneratorStep","gen","resolve","reject","_next","_throw","key","arg","info","value","error","done","Promise","then","_async_to_generator","fn","self1","args","arguments","apply","err","undefined","_ts_generator","thisArg","body","f","y","t","g","_","label","sent","trys","ops","next","verb","Symbol","iterator","n","v","step","op","TypeError","call","pop","length","push","e","CACHE_NAME","concat","APP_STATIC_RESOURCES","self","addEventListener","event","waitUntil","_state","caches","open","cache","addAll","keys","all","names","map","name","clients","claim","request","mode","url","endsWith","respondWith","match"],"version":3,"file":"sw-b71.js.map"} \ No newline at end of file diff --git a/dist/editor.1350aee5.js b/dist/editor.1350aee5.js deleted file mode 100644 index 19074bd..0000000 --- a/dist/editor.1350aee5.js +++ /dev/null @@ -1,209 +0,0 @@ -// modules are defined as an array -// [ module function, map of requires ] -// -// map of requires is short require name -> numeric require -// -// anything defined in a previous bundle is accessed via the -// orig method which is the require for previous bundles - -(function (modules, entry, mainEntry, parcelRequireName, globalName) { - /* eslint-disable no-undef */ - var globalObject = - typeof globalThis !== 'undefined' - ? globalThis - : typeof self !== 'undefined' - ? self - : typeof window !== 'undefined' - ? window - : typeof global !== 'undefined' - ? global - : {}; - /* eslint-enable no-undef */ - - // Save the require from previous bundle to this closure if any - var previousRequire = - typeof globalObject[parcelRequireName] === 'function' && - globalObject[parcelRequireName]; - - var cache = previousRequire.cache || {}; - // Do not use `require` to prevent Webpack from trying to bundle this call - var nodeRequire = - typeof module !== 'undefined' && - typeof module.require === 'function' && - module.require.bind(module); - - function newRequire(name, jumped) { - if (!cache[name]) { - if (!modules[name]) { - // if we cannot find the module within our internal map or - // cache jump to the current global require ie. the last bundle - // that was added to the page. - var currentRequire = - typeof globalObject[parcelRequireName] === 'function' && - globalObject[parcelRequireName]; - if (!jumped && currentRequire) { - return currentRequire(name, true); - } - - // If there are other bundles on this page the require from the - // previous one is saved to 'previousRequire'. Repeat this as - // many times as there are bundles until the module is found or - // we exhaust the require chain. - if (previousRequire) { - return previousRequire(name, true); - } - - // Try the node require function if it exists. - if (nodeRequire && typeof name === 'string') { - return nodeRequire(name); - } - - var err = new Error("Cannot find module '" + name + "'"); - err.code = 'MODULE_NOT_FOUND'; - throw err; - } - - localRequire.resolve = resolve; - localRequire.cache = {}; - - var module = (cache[name] = new newRequire.Module(name)); - - modules[name][0].call( - module.exports, - localRequire, - module, - module.exports, - globalObject - ); - } - - return cache[name].exports; - - function localRequire(x) { - var res = localRequire.resolve(x); - return res === false ? {} : newRequire(res); - } - - function resolve(x) { - var id = modules[name][1][x]; - return id != null ? id : x; - } - } - - function Module(moduleName) { - this.id = moduleName; - this.bundle = newRequire; - this.exports = {}; - } - - newRequire.isParcelRequire = true; - newRequire.Module = Module; - newRequire.modules = modules; - newRequire.cache = cache; - newRequire.parent = previousRequire; - newRequire.register = function (id, exports) { - modules[id] = [ - function (require, module) { - module.exports = exports; - }, - {}, - ]; - }; - - Object.defineProperty(newRequire, 'root', { - get: function () { - return globalObject[parcelRequireName]; - }, - }); - - globalObject[parcelRequireName] = newRequire; - - for (var i = 0; i < entry.length; i++) { - newRequire(entry[i]); - } - - if (mainEntry) { - // Expose entry point to Node, AMD or browser globals - // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js - var mainExports = newRequire(mainEntry); - - // CommonJS - if (typeof exports === 'object' && typeof module !== 'undefined') { - module.exports = mainExports; - - // RequireJS - } else if (typeof define === 'function' && define.amd) { - define(function () { - return mainExports; - }); - - // - - Level editor - - - - -
- - - - diff --git a/dist/index.html b/dist/index.html index 5da9a9e..51b9dd7 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,3686 +1 @@ - - - - - - - Breakout 71 - - - - - - - - - - - - - +Breakout 71 \ No newline at end of file diff --git a/src/PWA/sw-b71.js b/src/PWA/sw-b71.js index cff86af..bb7c6e5 100644 --- a/src/PWA/sw-b71.js +++ b/src/PWA/sw-b71.js @@ -1,5 +1,5 @@ // The version of the cache. -const VERSION = "29035871"; +const VERSION = "29036807"; // The name of the cache const CACHE_NAME = `breakout-71-${VERSION}`; diff --git a/src/data/version.json b/src/data/version.json index cb71d2b..692db8e 100644 --- a/src/data/version.json +++ b/src/data/version.json @@ -1 +1 @@ -"29035871" +"29036807" diff --git a/src/game.ts b/src/game.ts index d71f7dd..8ec4402 100644 --- a/src/game.ts +++ b/src/game.ts @@ -52,12 +52,6 @@ import { } from "./asyncAlert"; import { isOptionOn, options, toggleOption } from "./options"; -bombSVG.src = - "data:image/svg+xml;base64," + - btoa(` - -`); - export function play() { if (gameState.running) return; gameState.running = true; @@ -261,8 +255,8 @@ gameCanvas.addEventListener("mouseup", (e) => { pause(true); } else { play(); - if (isOptionOn("pointerLock")) { - gameCanvas.requestPointerLock(); + if (isOptionOn("pointerLock") && gameCanvas.requestPointerLock) { + gameCanvas.requestPointerLock().then(); } } }); @@ -543,7 +537,6 @@ async function openSettingsPanel() { for (const key of Object.keys(options) as OptionId[]) { if (options[key]) actions.push({ - disabled: options[key].disabled(), icon: isOptionOn(key) ? icons["icon:checkmark_checked"] : icons["icon:checkmark_unchecked"], @@ -655,6 +648,116 @@ async function openSettingsPanel() { }, }); + actions.push({ + text: t("main_menu.download_save_file"), + help: t("main_menu.download_save_file_help"), + async value() { + const localStorageContent: Record = {}; + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i) as string; + const value = localStorage.getItem(key) as string; + + // Store the key-value pair in the object + localStorageContent[key] = value; + } + + const dlLink = document.createElement("a"); + + dlLink.setAttribute( + "href", + "data:application/json;base64," + + btoa( + JSON.stringify({ + fileType: "B71-save-file", + appVersion, + localStorageContent, + }), + ), + ); + + dlLink.setAttribute( + "download", + "b71-save-" + + new Date() + .toISOString() + .slice(0, 19) + .replace(/[^0-9]+/gi, "-") + + ".b71", + ); + document.body.appendChild(dlLink); + dlLink.click(); + setTimeout(() => document.body.removeChild(dlLink), 1000); + }, + }); + + actions.push({ + text: t("main_menu.load_save_file"), + help: t("main_menu.load_save_file_help"), + async value() { + if (!document.getElementById("save_file_picker")) { + let input: HTMLInputElement = document.createElement("input"); + input.setAttribute("type", "file"); + input.setAttribute("id", "save_file_picker"); + input.setAttribute("accept", ".b71"); + input.style.position = "absolute"; + input.style.left = "-1000px"; + input.addEventListener("change", async (e) => { + try { + const file = input && input.files?.item(0); + if (file) { + const content = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = function () { + resolve(reader.result?.toString() || ""); + }; + reader.onerror = function () { + reject(reader.error); + }; + + // Read the file as a text string + + reader.readAsText(file); + }); + const { + fileType, + appVersion: fileVersion, + localStorageContent, + } = JSON.parse(content); + if (fileType !== "B71-save-file") + throw new Error("Not a B71 save file"); + if (fileVersion > appVersion) + throw new Error( + "Please update your app first, this file is for version " + + fileVersion + + " or newer.", + ); + localStorage.clear(); + for (let key in localStorageContent) { + localStorage.setItem(key, localStorageContent[key]); + } + await asyncAlert({ + title: t("main_menu.save_file_loaded"), + text: t("main_menu.save_file_loaded_help"), + actions: [{ text: t("main_menu.save_file_loaded_ok") }], + }); + window.location.reload(); + } + } catch (e: any) { + await asyncAlert({ + title: t("main_menu.save_file_error"), + text: e.message, + actions: [{ text: t("main_menu.save_file_loaded_ok") }], + }); + } + input.value = ""; + }); + document.body.appendChild(input); + } + document.getElementById("save_file_picker")?.click(); + }, + }); + actions.push({ text: t("main_menu.language"), help: t("main_menu.language_help"), diff --git a/src/i18n/b71.babel b/src/i18n/b71.babel index f102ab0..d58380e 100644 --- a/src/i18n/b71.babel +++ b/src/i18n/b71.babel @@ -622,6 +622,36 @@ + + download_save_file + + + + + en-US + false + + + fr-FR + false + + + + + download_save_file_help + + + + + en-US + false + + + fr-FR + false + + + footer_html @@ -757,6 +787,36 @@ + + load_save_file + + + + + en-US + false + + + fr-FR + false + + + + + load_save_file_help + + + + + en-US + false + + + fr-FR + false + + + mobile @@ -967,6 +1027,66 @@ + + save_file_error + + + + + en-US + false + + + fr-FR + false + + + + + save_file_loaded + + + + + en-US + false + + + fr-FR + false + + + + + save_file_loaded_help + + + + + en-US + false + + + fr-FR + false + + + + + save_file_loaded_ok + + + + + en-US + false + + + fr-FR + false + + + sounds diff --git a/src/i18n/en.json b/src/i18n/en.json index aef389c..1daabef 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -37,6 +37,8 @@ "level_up.upgrade_perk_to_level": " lvl {{level}}", "main_menu.basic": "Basic graphics", "main_menu.basic_help": "Fewer particles and flashes, better performance.", + "main_menu.download_save_file": "Download save file", + "main_menu.download_save_file_help": "Get a transferable .b71 file with your score and stats", "main_menu.footer_html": "

Made in France by Renan LE CARO. \n Privacy Policy\n F-Droid\n Google Play\n itch.io \n Gitlab\n Web version\n HackerNews\n v.{{appVersion}}

", "main_menu.fullscreen": "Fullscreen", "main_menu.fullscreen_exit": "Exit Fullscreen", @@ -46,6 +48,8 @@ "main_menu.kid_help": "Start future runs with \"slower ball\".", "main_menu.language": "Language", "main_menu.language_help": "Choose the game's language", + "main_menu.load_save_file": "Load save file", + "main_menu.load_save_file_help": "Select a .b71 file on your device", "main_menu.mobile": "Mobile mode", "main_menu.mobile_help": "Leaves space for your thumb under the puck.", "main_menu.pointer_lock": "Mouse pointer lock", @@ -60,6 +64,10 @@ "main_menu.reset_instruction": "You will loose all progress you made in the game, are you sure ?", "main_menu.resume": "Resume", "main_menu.resume_help": "Return to your run", + "main_menu.save_file_error": "Error loading save file", + "main_menu.save_file_loaded": "Save file loaded", + "main_menu.save_file_loaded_help": "The app will now reload to apply your save", + "main_menu.save_file_loaded_ok": "Ok", "main_menu.sounds": "Game sounds", "main_menu.sounds_help": "Can slow down some phones.", "main_menu.title": "Breakout 71", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 8eef790..249ca3c 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -37,6 +37,8 @@ "level_up.upgrade_perk_to_level": " niveau {{level}}", "main_menu.basic": "Graphismes simplifiés", "main_menu.basic_help": "Moins de particules et effets, meilleures performances.", + "main_menu.download_save_file": "Sauvegarder mes progrès", + "main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde .b71 transférable", "main_menu.footer_html": "

Programmé en France par Renan LE CARO. Politique de confidentialité F-Droid Google Play itch.io Gitlab Version web HackerNews v.{{appVersion}}

", "main_menu.fullscreen": "Plein écran", "main_menu.fullscreen_exit": "Quitter le plein écran", @@ -46,6 +48,8 @@ "main_menu.kid_help": "Balle plus lente", "main_menu.language": "Langue", "main_menu.language_help": "Changer la langue d'affichage", + "main_menu.load_save_file": "Charger une sauvegarde", + "main_menu.load_save_file_help": "Sélectionnez un fichier .b71 sur votre appareil ", "main_menu.mobile": "Mode mobile", "main_menu.mobile_help": "Laisse un espace pour le pouce sous le palet.", "main_menu.pointer_lock": "Verrouillage du pointeur de la souris", @@ -60,6 +64,10 @@ "main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?", "main_menu.resume": "Retourner à la partie", "main_menu.resume_help": "Continuer la partie en cours", + "main_menu.save_file_error": "Erreur lors du chargement du fichier de sauvegarde", + "main_menu.save_file_loaded": "Sauvegarde chargée", + "main_menu.save_file_loaded_help": "L'appli va redémarrer", + "main_menu.save_file_loaded_ok": "Ok", "main_menu.sounds": "Sons du jeu", "main_menu.sounds_help": "Ralentis certains téléphones.", "main_menu.title": "Breakout 71", diff --git a/src/options.ts b/src/options.ts index 3372134..22a8e18 100644 --- a/src/options.ts +++ b/src/options.ts @@ -8,40 +8,32 @@ export const options = { default: true, name: t("main_menu.sounds"), help: t("main_menu.sounds_help"), - disabled: () => false, }, "mobile-mode": { default: window.innerHeight > window.innerWidth, name: t("main_menu.mobile"), help: t("main_menu.mobile_help"), - disabled: () => false, }, basic: { default: false, name: t("main_menu.basic"), help: t("main_menu.basic_help"), - disabled: () => false, }, pointerLock: { default: false, name: t("main_menu.pointer_lock"), help: t("main_menu.pointer_lock_help"), - disabled: () => !document.body.requestPointerLock, }, easy: { default: false, name: t("main_menu.kid"), help: t("main_menu.kid_help"), - disabled: () => false, }, // Could not get the sharing to work without loading androidx and all the modern android things so for now I'll just disable sharing in the android app record: { default: false, name: t("main_menu.record"), help: t("main_menu.record_help"), - disabled() { - return window.location.search.includes("isInWebView=true"); - }, }, } as const satisfies { [k: string]: OptionDef }; diff --git a/src/render.ts b/src/render.ts index 6f77e2b..351a086 100644 --- a/src/render.ts +++ b/src/render.ts @@ -16,6 +16,13 @@ export const ctx = gameCanvas.getContext("2d", { alpha: false, }) as CanvasRenderingContext2D; export const bombSVG = document.createElement("img"); + +bombSVG.src = + "data:image/svg+xml;base64," + + btoa(` + +`); + export const background = document.createElement("img"); export const backgroundCanvas = document.createElement("canvas"); diff --git a/src/types.d.ts b/src/types.d.ts index 67fb742..6966533 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -248,6 +248,5 @@ export type OptionDef = { default: boolean; name: string; help: string; - disabled: () => boolean; }; export type OptionId = keyof typeof options;