From 26c81d867d89bf6612520405489ffaee21912253 Mon Sep 17 00:00:00 2001 From: EliDoris Date: Fri, 21 Nov 2025 22:05:39 -0500 Subject: [PATCH 1/5] Added functionality to automatically save board states in localStorage --- src-ui/js/ui/Boot.js | 90 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/src-ui/js/ui/Boot.js b/src-ui/js/ui/Boot.js index a0526f397..bace95d3c 100644 --- a/src-ui/js/ui/Boot.js +++ b/src-ui/js/ui/Boot.js @@ -1,6 +1,7 @@ // Boot.js v3.4.0 (function() { + /********************************/ /* 初期化時のみ使用するルーチン */ /********************************/ @@ -13,19 +14,35 @@ // ★boot() window.onload直後の処理 //--------------------------------------------------------------------------- pzpr.on("load", function boot() { - if (importData()) { - startPuzzle(); + var pzl; + // Get URL search hash and check localStorage to see if a board state is saved + if (!localStorageAvailable()) { + pzl = importData() } else { - setTimeout(boot, 0); + var key = 'pzpr_' + getPuzzleString() + var puzzleStr = localStorage.getItem(key) + if (!puzzleStr) { + pzl = importData() + } else { + pzl = importData(puzzleStr) // Local storage was available and key was found + } + } + if (!pzl) { + setTimeout(boot,0); } + startPuzzle(); }); - function importData() { + function importData(string) { if (!onload_pzl) { /* 1) 盤面複製・index.htmlからのファイル入力/Database入力か */ /* 2) URL(?以降)をチェック */ - onload_pzl = importURL(); - + if (!string) { + onload_pzl = importURL(); + } else { + onload_pzl = importFromString(string); + } + /* 指定されたパズルがない場合はさようなら~ */ if (!onload_pzl || !onload_pzl.pid) { failOpen(); @@ -90,26 +107,77 @@ //--------------------------------------------------------------------------- function importURL() { /* index.htmlからURLが入力されていない場合は現在のURLの?以降をとってくる */ + var puzString = getPuzzleString(); + return importFromString(puzString); + } + //Splitting functionality from above for flexibility. + + //Return the string associated with the puzzle + function getPuzzleString() { var search = location.search; if (!search) { return null; } - - /* 一旦先頭の?記号を取り除く */ if (search.charAt(0) === "?") { - search = search.substr(1); + search = search.slice(1); //Non-deprecated version of substr } while (search.match(/^(\w+)\=(\w+)\&(.*)/)) { onload_option[RegExp.$1] = RegExp.$2; search = RegExp.$3; } + return search + } + //Import from a puzzle string. This can come from the URL or from localStorage + function importFromString(string) { + if (!string) { + return null; + } - onload_pzv = search; - var pzl = pzpr.parser.parseURL(search); + onload_pzv = string; + var pzl = pzpr.parser.parseURL(string); var startmode = pzl.mode || (!pzl.body ? "editor" : "player"); onload_option.type = onload_option.type || startmode; return pzl; } + + //--------------------------------------------------------------------------- + // Functionality to support browser caching + //--------------------------------------------------------------------------- + + //Taken directly from stackoverflow. Apparently this is the most broadly compatible version. https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available + function localStorageAvailable() { + var test = 'test'; + try { + localStorage.setItem(test,test); + localStorage.removeItem(test) + return true; + } catch (e) { + return false; + } + } + + //Save board state. Creates an entry in localStorage whose key is a 'pzpr_' identifier plus the current board state puzzle string. + //Board state puzzle string is the same thing you get from duplicating the board state + function saveBoardState() { + var key = 'pzpr_' + getPuzzleString() + var url = ui.puzzle.getURL( + pzpr.parser.URL_PZPRFILE, + ui.puzzle.playeronly ? "player" : "editor" + ); + //Strip url to the last option. This is the "puzzle string" we want + url = url.substring(url.indexOf('?')+1) //Skip to the search parameters part of the url + while (url.match(/^(\w+)\=(\w+)\&(.*)/)) { + url = RegExp.$3; + } + localStorage.setItem(key,url) + } + + //Events that trigger a board state save + document.addEventListener("visibilitychange", function() { + if (document.visibilityState === "hidden") { + saveBoardState(); + } + }); })(); From ab6159eec779a3c3ae165fa47da7ea872fff2870 Mon Sep 17 00:00:00 2001 From: EliDoris Date: Fri, 21 Nov 2025 22:31:31 -0500 Subject: [PATCH 2/5] Add try/catch around setItem --- src-ui/js/ui/Boot.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src-ui/js/ui/Boot.js b/src-ui/js/ui/Boot.js index bace95d3c..91f995967 100644 --- a/src-ui/js/ui/Boot.js +++ b/src-ui/js/ui/Boot.js @@ -171,7 +171,11 @@ while (url.match(/^(\w+)\=(\w+)\&(.*)/)) { url = RegExp.$3; } - localStorage.setItem(key,url) + try { + localStorage.setItem(key,url) + } catch (e) { + console.log(e) + } } //Events that trigger a board state save From cd7f7af8d02e74ac34471dba0072876c09cb65df Mon Sep 17 00:00:00 2001 From: EliDoris Date: Fri, 21 Nov 2025 22:45:00 -0500 Subject: [PATCH 3/5] Formatting --- src-ui/js/ui/Boot.js | 45 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src-ui/js/ui/Boot.js b/src-ui/js/ui/Boot.js index 91f995967..4534fde28 100644 --- a/src-ui/js/ui/Boot.js +++ b/src-ui/js/ui/Boot.js @@ -1,7 +1,6 @@ // Boot.js v3.4.0 (function() { - /********************************/ /* 初期化時のみ使用するルーチン */ /********************************/ @@ -17,18 +16,18 @@ var pzl; // Get URL search hash and check localStorage to see if a board state is saved if (!localStorageAvailable()) { - pzl = importData() + pzl = importData(); } else { - var key = 'pzpr_' + getPuzzleString() - var puzzleStr = localStorage.getItem(key) + var key = "pzpr_" + getPuzzleString(); + var puzzleStr = localStorage.getItem(key); if (!puzzleStr) { - pzl = importData() + pzl = importData(); } else { - pzl = importData(puzzleStr) // Local storage was available and key was found + pzl = importData(puzzleStr); // Local storage was available and key was found } } if (!pzl) { - setTimeout(boot,0); + setTimeout(boot, 0); } startPuzzle(); }); @@ -42,7 +41,7 @@ } else { onload_pzl = importFromString(string); } - + /* 指定されたパズルがない場合はさようなら~ */ if (!onload_pzl || !onload_pzl.pid) { failOpen(); @@ -126,7 +125,7 @@ onload_option[RegExp.$1] = RegExp.$2; search = RegExp.$3; } - return search + return search; } //Import from a puzzle string. This can come from the URL or from localStorage function importFromString(string) { @@ -148,10 +147,10 @@ //Taken directly from stackoverflow. Apparently this is the most broadly compatible version. https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available function localStorageAvailable() { - var test = 'test'; + var test = "test"; try { - localStorage.setItem(test,test); - localStorage.removeItem(test) + localStorage.setItem(test, test); + localStorage.removeItem(test); return true; } catch (e) { return false; @@ -161,27 +160,27 @@ //Save board state. Creates an entry in localStorage whose key is a 'pzpr_' identifier plus the current board state puzzle string. //Board state puzzle string is the same thing you get from duplicating the board state function saveBoardState() { - var key = 'pzpr_' + getPuzzleString() + var key = "pzpr_" + getPuzzleString(); var url = ui.puzzle.getURL( - pzpr.parser.URL_PZPRFILE, - ui.puzzle.playeronly ? "player" : "editor" - ); + pzpr.parser.URL_PZPRFILE, + ui.puzzle.playeronly ? "player" : "editor" + ); //Strip url to the last option. This is the "puzzle string" we want - url = url.substring(url.indexOf('?')+1) //Skip to the search parameters part of the url + url = url.substring(url.indexOf("?") + 1); //Skip to the search parameters part of the url while (url.match(/^(\w+)\=(\w+)\&(.*)/)) { url = RegExp.$3; } try { - localStorage.setItem(key,url) + localStorage.setItem(key, url); } catch (e) { - console.log(e) + console.log(e); } } //Events that trigger a board state save document.addEventListener("visibilitychange", function() { - if (document.visibilityState === "hidden") { - saveBoardState(); - } - }); + if (document.visibilityState === "hidden") { + saveBoardState(); + } + }); })(); From 7f32c6c3728fb6f40476284cbc6cc066004ec433 Mon Sep 17 00:00:00 2001 From: EliDoris Date: Sat, 20 Dec 2025 11:57:39 -0500 Subject: [PATCH 4/5] Change caching code to handle out-of-storage errors. Move localStorage detection --- src-ui/js/ui/Boot.js | 58 ++++++++++++++++++++++++++++++-------------- src/pzpr/env.js | 14 ++++++++++- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src-ui/js/ui/Boot.js b/src-ui/js/ui/Boot.js index 4534fde28..689d15d11 100644 --- a/src-ui/js/ui/Boot.js +++ b/src-ui/js/ui/Boot.js @@ -15,15 +15,16 @@ pzpr.on("load", function boot() { var pzl; // Get URL search hash and check localStorage to see if a board state is saved - if (!localStorageAvailable()) { + if (!pzpr.env.localStorageAvailable) { pzl = importData(); } else { var key = "pzpr_" + getPuzzleString(); - var puzzleStr = localStorage.getItem(key); - if (!puzzleStr) { + var valStr = localStorage.getItem(key); + if (!valStr) { pzl = importData(); } else { - pzl = importData(puzzleStr); // Local storage was available and key was found + var valObject = JSON.parse(valStr) + pzl = importData(valObject.pzl); // Local storage was available and key was found } } if (!pzl) { @@ -145,18 +146,6 @@ // Functionality to support browser caching //--------------------------------------------------------------------------- - //Taken directly from stackoverflow. Apparently this is the most broadly compatible version. https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available - function localStorageAvailable() { - var test = "test"; - try { - localStorage.setItem(test, test); - localStorage.removeItem(test); - return true; - } catch (e) { - return false; - } - } - //Save board state. Creates an entry in localStorage whose key is a 'pzpr_' identifier plus the current board state puzzle string. //Board state puzzle string is the same thing you get from duplicating the board state function saveBoardState() { @@ -170,10 +159,43 @@ while (url.match(/^(\w+)\=(\w+)\&(.*)/)) { url = RegExp.$3; } + //Add a time signifier so that we can sort and delete oldest if setting fails + var valObject = { + t: Date.now(), + pzl: url + // bufferToForceStorageLimitErrors: "0".repeat(1700000) //Include for testing to force out-of-storage errors + } try { - localStorage.setItem(key, url); + localStorage.setItem(key, JSON.stringify(valObject)); } catch (e) { - console.log(e); + if (e.name === "QuotaExceededError") { + //If storage was full: load all of the puzzles in localStorage, sort by least recent, and delete until saving is successful + var saveSuccess = false + var pairs = [] + for (var i = 0; i < localStorage.length; i++) { + var lsKey = localStorage.key(i) + var lsValue = localStorage.getItem(lsKey) + pairs.push({key: lsKey, value: lsValue}) + } + pairs = pairs.filter(function(item) { + return item.key.indexOf("pzpr_") === 0 + }) + pairs = pairs.sort(function(a,b) { + var ta = JSON.parse(a.value).t + var tb = JSON.parse(b.value).t + return ta > tb + }) + while (!saveSuccess && pairs.length > 0) { + console.log(pairs) + try { + localStorage.setItem(key, JSON.stringify(valObject)); + saveSuccess = true + } catch (e) { + localStorage.removeItem(pairs[0].key) + pairs = pairs.slice(1) + } + } + } } } diff --git a/src/pzpr/env.js b/src/pzpr/env.js index 4a23ec3a3..61ee9041b 100644 --- a/src/pzpr/env.js +++ b/src/pzpr/env.js @@ -64,13 +64,25 @@ pzpr.env = (function() { anchor_download: isbrowser && document.createElement("a").download !== void 0 }; + //Taken directly from stackoverflow. Apparently this is the most broadly compatible version. https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available + var localStorageAvailable = (function() { + var test = "test"; + try { + localStorage.setItem(test, test); + localStorage.removeItem(test); + return true; + } catch (e) { + return false; + } + })(); return { bz: bz, OS: os, API: api, browser: isbrowser, - node: pzpr.Candle.env.node + node: pzpr.Candle.env.node, + localStorageAvailable: localStorageAvailable }; })(); From 7d4a54f48eefe73713337610663359ba7e46cb8c Mon Sep 17 00:00:00 2001 From: EliDoris Date: Sat, 20 Dec 2025 12:02:58 -0500 Subject: [PATCH 5/5] Forgot to run prettier --- src-ui/js/ui/Boot.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src-ui/js/ui/Boot.js b/src-ui/js/ui/Boot.js index 689d15d11..e93d11716 100644 --- a/src-ui/js/ui/Boot.js +++ b/src-ui/js/ui/Boot.js @@ -23,7 +23,7 @@ if (!valStr) { pzl = importData(); } else { - var valObject = JSON.parse(valStr) + var valObject = JSON.parse(valStr); pzl = importData(valObject.pzl); // Local storage was available and key was found } } @@ -164,35 +164,35 @@ t: Date.now(), pzl: url // bufferToForceStorageLimitErrors: "0".repeat(1700000) //Include for testing to force out-of-storage errors - } + }; try { localStorage.setItem(key, JSON.stringify(valObject)); } catch (e) { if (e.name === "QuotaExceededError") { //If storage was full: load all of the puzzles in localStorage, sort by least recent, and delete until saving is successful - var saveSuccess = false - var pairs = [] + var saveSuccess = false; + var pairs = []; for (var i = 0; i < localStorage.length; i++) { - var lsKey = localStorage.key(i) - var lsValue = localStorage.getItem(lsKey) - pairs.push({key: lsKey, value: lsValue}) + var lsKey = localStorage.key(i); + var lsValue = localStorage.getItem(lsKey); + pairs.push({ key: lsKey, value: lsValue }); } pairs = pairs.filter(function(item) { - return item.key.indexOf("pzpr_") === 0 - }) - pairs = pairs.sort(function(a,b) { - var ta = JSON.parse(a.value).t - var tb = JSON.parse(b.value).t - return ta > tb - }) + return item.key.indexOf("pzpr_") === 0; + }); + pairs = pairs.sort(function(a, b) { + var ta = JSON.parse(a.value).t; + var tb = JSON.parse(b.value).t; + return ta > tb; + }); while (!saveSuccess && pairs.length > 0) { - console.log(pairs) + console.log(pairs); try { localStorage.setItem(key, JSON.stringify(valObject)); - saveSuccess = true + saveSuccess = true; } catch (e) { - localStorage.removeItem(pairs[0].key) - pairs = pairs.slice(1) + localStorage.removeItem(pairs[0].key); + pairs = pairs.slice(1); } } }