From c7fd17183d27ccd70aac70f89a832e3448136ad3 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 19 Feb 2019 22:45:02 -0500 Subject: [PATCH 1/8] Add meta viewport tag to all html pages --- ext/bg/background.html | 1 + ext/bg/context.html | 1 + ext/bg/guide.html | 1 + ext/bg/legal.html | 1 + ext/bg/search.html | 1 + ext/bg/settings.html | 1 + ext/fg/float.html | 1 + 7 files changed, 7 insertions(+) diff --git a/ext/bg/background.html b/ext/bg/background.html index 97b20f46..3262f2a1 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -2,6 +2,7 @@ + diff --git a/ext/bg/context.html b/ext/bg/context.html index 8a72acc7..01b4fb30 100644 --- a/ext/bg/context.html +++ b/ext/bg/context.html @@ -2,6 +2,7 @@ + diff --git a/ext/bg/guide.html b/ext/bg/guide.html index 4b01ae7c..6f98d264 100644 --- a/ext/bg/guide.html +++ b/ext/bg/guide.html @@ -2,6 +2,7 @@ + Welcome to Yomichan! diff --git a/ext/bg/legal.html b/ext/bg/legal.html index a289281d..28c7fb21 100644 --- a/ext/bg/legal.html +++ b/ext/bg/legal.html @@ -2,6 +2,7 @@ + Yomichan Legal diff --git a/ext/bg/search.html b/ext/bg/search.html index 5fbd467a..0d6c7cad 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -2,6 +2,7 @@ + Yomichan Search diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 7f18a358..3728876c 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -2,6 +2,7 @@ + Yomichan Options diff --git a/ext/fg/float.html b/ext/fg/float.html index 89872cce..fed7eeab 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -2,6 +2,7 @@ + From d49cbf12eae40e1a898c619ed092af560aa91bc6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 19 Feb 2019 22:47:27 -0500 Subject: [PATCH 2/8] Add search link and padding to settings page links This makes the bottom links easier to touch and makes the search page easier to access when there is no extension badge --- ext/bg/settings.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3728876c..53d17855 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -25,6 +25,10 @@ overflow-x: hidden; white-space: pre; } + + .bottom-links { + padding-bottom: 1em; + } @@ -311,8 +315,8 @@

 
-            
- HomepageLegal +
From 769dc205fb56fa8f9442f68907eb9598caea52bd Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 19 Feb 2019 22:49:25 -0500 Subject: [PATCH 3/8] Make extension badge and onCommand optional --- ext/bg/js/backend.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 01340419..c191a150 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -28,7 +28,9 @@ class Backend { await this.translator.prepare(); await apiOptionsSet(await optionsLoad()); - chrome.commands.onCommand.addListener(this.onCommand.bind(this)); + if (chrome.commands !== null && typeof chrome.commands === 'object') { + chrome.commands.onCommand.addListener(this.onCommand.bind(this)); + } chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); if (this.options.general.showGuide) { @@ -40,13 +42,13 @@ class Backend { this.options = utilIsolate(options); if (!options.general.enable) { - chrome.browserAction.setBadgeBackgroundColor({color: '#555555'}); - chrome.browserAction.setBadgeText({text: 'off'}); + this.setExtensionBadgeBackgroundColor('#555555'); + this.setExtensionBadgeText('off'); } else if (!dictConfigured(options)) { - chrome.browserAction.setBadgeBackgroundColor({color: '#f0ad4e'}); - chrome.browserAction.setBadgeText({text: '!'}); + this.setExtensionBadgeBackgroundColor('#f0ad4e'); + this.setExtensionBadgeText('!'); } else { - chrome.browserAction.setBadgeText({text: ''}); + this.setExtensionBadgeText(''); } if (options.anki.enable) { @@ -125,6 +127,18 @@ class Backend { return true; } + + setExtensionBadgeBackgroundColor(color) { + if (typeof chrome.browserAction.setBadgeBackgroundColor === 'function') { + chrome.browserAction.setBadgeBackgroundColor({color}); + } + } + + setExtensionBadgeText(text) { + if (typeof chrome.browserAction.setBadgeText === 'function') { + chrome.browserAction.setBadgeText({text}); + } + } } window.yomichan_backend = new Backend(); From c933a55b818052c0a0922257a9fb82928723850a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 19 Feb 2019 22:50:33 -0500 Subject: [PATCH 4/8] Handle messages with unexpected response format The response parameter can be undefined --- ext/fg/js/util.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js index 5eff4018..954b3988 100644 --- a/ext/fg/js/util.js +++ b/ext/fg/js/util.js @@ -26,11 +26,15 @@ function utilAsync(func) { function utilInvoke(action, params={}) { return new Promise((resolve, reject) => { try { - chrome.runtime.sendMessage({action, params}, ({result, error}) => { - if (error) { - reject(error); + chrome.runtime.sendMessage({action, params}, (response) => { + if (response !== null && typeof response === 'object') { + if (response.error) { + reject(response.error); + } else { + resolve(response.result); + } } else { - resolve(result); + reject(`Unexpected response of type ${typeof response}`); } }); } catch (e) { From 884be644ecae69e2e2c30e9f4cccf897532f942f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 19 Feb 2019 22:51:48 -0500 Subject: [PATCH 5/8] Make error loging unintrusive This alert can show up under normal use circumstances on mobile --- ext/fg/js/frontend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index a81cbef8..bd652f3b 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -256,7 +256,7 @@ class Frontend { } onError(error) { - window.alert(`Error: ${error.toString ? error.toString() : error}`); + console.log(error); } popupTimerSet(callback) { From 2328d61a8135ec3b4ca85a9d823ccdbf38b94e84 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 4 May 2019 12:57:55 -0400 Subject: [PATCH 6/8] Add storage information to settings page --- ext/bg/js/settings.js | 81 +++++++++++++++++++++++++++++++++++++++++++ ext/bg/settings.html | 47 ++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 4bf7181f..579af085 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -203,6 +203,8 @@ async function onReady() { } formUpdateVisibility(options); + + storageInfoInitialize(); } $(document).ready(utilAsync(onReady)); @@ -520,3 +522,82 @@ async function onAnkiFieldTemplatesReset(e) { ankiErrorShow(e); } } + + +/* + * Storage + */ + +async function getBrowser() { + if (typeof chrome !== "undefined") { + if (typeof browser !== "undefined") { + try { + const info = await browser.runtime.getBrowserInfo(); + if (info.name === "Fennec") { + return "firefox-mobile"; + } + } catch (e) { } + return "firefox"; + } else { + return "chrome"; + } + } else { + return "edge"; + } +} + +function storageBytesToLabeledString(size) { + const base = 1000; + const labels = ["bytes", "KB", "MB", "GB"]; + let labelIndex = 0; + while (size >= base) { + size /= base; + ++labelIndex; + } + const label = size.toFixed(1).replace(/\.0+$/, ""); + return `${label}${labels[labelIndex]}`; +} + +async function storageEstimate() { + try { + return await navigator.storage.estimate(); + } catch (e) { } + return null; +} + +async function storageInfoInitialize() { + const browser = await getBrowser(); + const container = document.querySelector("#storage-info"); + container.setAttribute("data-browser", browser); + + await storageShowInfo(); + + container.classList.remove("storage-hidden"); + + document.querySelector("#storage-refresh").addEventListener('click', () => storageShowInfo(), false); +} + +async function storageShowInfo() { + storageSpinnerShow(true); + + const estimate = await storageEstimate(); + const valid = (estimate !== null); + + if (valid) { + document.querySelector("#storage-usage").textContent = storageBytesToLabeledString(estimate.usage); + document.querySelector("#storage-quota").textContent = storageBytesToLabeledString(estimate.quota); + } + document.querySelector("#storage-use").classList.toggle("storage-hidden", !valid); + document.querySelector("#storage-error").classList.toggle("storage-hidden", valid); + + storageSpinnerShow(false); +} + +function storageSpinnerShow(show) { + const spinner = $('#storage-spinner'); + if (show) { + spinner.show(); + } else { + spinner.hide(); + } +} diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 53d17855..d41d442b 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -9,7 +9,7 @@ @@ -197,6 +208,40 @@ +
+
+ +

Storage

+
+ +
+

+ Yomichan is using approximately of . +

+
+ +
+

+ Could not detect how much storage Yomichan is using. +

+
+ On Firefox and Firefox for Android, the storage information feature may be hidden behind a browser flag. + + If you would like to enable this flag, open about:config and search for the + dom.storageManager.enabled option. If this option has a value of false, toggling it to + true may allow storage information to be calculated. +
+
+ +
+ If you are using Firefox for Android, you will have to make sure you have enough free space on your device to install dictionaries. +
+ +
+ +
+
+
From d96d4b0658de9eeb272a57966a07a7798da09433 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 26 Feb 2019 21:01:32 -0500 Subject: [PATCH 7/8] Passively handle database errors --- ext/bg/js/database.js | 68 ++++++++++++++++++++------------ ext/bg/js/settings.js | 92 +++++++++++++++++++++++++++++-------------- ext/bg/js/util.js | 4 +- 3 files changed, 107 insertions(+), 57 deletions(-) diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 3c7f6aab..093ec102 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -228,11 +228,47 @@ class Database { } } - async importDictionary(archive, callback) { + async importDictionary(archive, progressCallback, exceptions) { if (!this.db) { throw 'Database not initialized'; } + const maxTransactionLength = 1000; + const bulkAdd = async (table, items, total, current) => { + if (items.length < maxTransactionLength) { + if (progressCallback) { + progressCallback(total, current); + } + + try { + await table.bulkAdd(items); + } catch (e) { + if (exceptions) { + exceptions.push(e); + } else { + throw e; + } + } + } else { + for (let i = 0; i < items.length; i += maxTransactionLength) { + if (progressCallback) { + progressCallback(total, current + i / items.length); + } + + let count = Math.min(maxTransactionLength, items.length - i); + try { + await table.bulkAdd(items.slice(i, i + count)); + } catch (e) { + if (exceptions) { + exceptions.push(e); + } else { + throw e; + } + } + } + } + }; + const indexDataLoaded = async summary => { if (summary.version > 3) { throw 'Unsupported dictionary version'; @@ -247,10 +283,6 @@ class Database { }; const termDataLoaded = async (summary, entries, total, current) => { - if (callback) { - callback(total, current); - } - const rows = []; if (summary.version === 1) { for (const [expression, reading, definitionTags, rules, score, ...glossary] of entries) { @@ -280,14 +312,10 @@ class Database { } } - await this.db.terms.bulkAdd(rows); + await bulkAdd(this.db.terms, rows, total, current); }; const termMetaDataLoaded = async (summary, entries, total, current) => { - if (callback) { - callback(total, current); - } - const rows = []; for (const [expression, mode, data] of entries) { rows.push({ @@ -298,14 +326,10 @@ class Database { }); } - await this.db.termMeta.bulkAdd(rows); + await bulkAdd(this.db.termMeta, rows, total, current); }; const kanjiDataLoaded = async (summary, entries, total, current) => { - if (callback) { - callback(total, current); - } - const rows = []; if (summary.version === 1) { for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) { @@ -332,14 +356,10 @@ class Database { } } - await this.db.kanji.bulkAdd(rows); + await bulkAdd(this.db.kanji, rows, total, current); }; const kanjiMetaDataLoaded = async (summary, entries, total, current) => { - if (callback) { - callback(total, current); - } - const rows = []; for (const [character, mode, data] of entries) { rows.push({ @@ -350,14 +370,10 @@ class Database { }); } - await this.db.kanjiMeta.bulkAdd(rows); + await bulkAdd(this.db.kanjiMeta, rows, total, current); }; const tagDataLoaded = async (summary, entries, total, current) => { - if (callback) { - callback(total, current); - } - const rows = []; for (const [name, category, order, notes, score] of entries) { const row = dictTagSanitize({ @@ -372,7 +388,7 @@ class Database { rows.push(row); } - await this.db.tagMeta.bulkAdd(rows); + await bulkAdd(this.db.tagMeta, rows, total, current); }; return await Database.importDictionaryZip( diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 579af085..99463593 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -193,7 +193,7 @@ async function onReady() { await dictionaryGroupsPopulate(options); await formMainDictionaryOptionsPopulate(options); } catch (e) { - dictionaryErrorShow(e); + dictionaryErrorsShow([e]); } try { @@ -214,36 +214,63 @@ $(document).ready(utilAsync(onReady)); * Dictionary */ -function dictionaryErrorShow(error) { +function dictionaryErrorToString(error) { + if (error.toString) { + error = error.toString(); + } else { + error = `${error}`; + } + + for (const [match, subst] of dictionaryErrorToString.overrides) { + if (error.includes(match)) { + error = subst; + break; + } + } + + return error; +} +dictionaryErrorToString.overrides = [ + [ + 'A mutation operation was attempted on a database that did not allow mutations.', + 'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.' + ], + [ + 'The operation failed for reasons unrelated to the database itself and not covered by any other error code.', + 'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.' + ], + [ + 'BulkError', + 'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.' + ] +]; + +function dictionaryErrorsShow(errors) { const dialog = $('#dict-error'); - if (error) { - const overrides = [ - [ - 'A mutation operation was attempted on a database that did not allow mutations.', - 'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.' - ], - [ - 'The operation failed for reasons unrelated to the database itself and not covered by any other error code.', - 'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.' - ], - [ - 'BulkError', - 'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.' - ] - ]; + dialog.show().text(''); - if (error.toString) { - error = error.toString(); + if (errors !== null && errors.length > 0) { + const uniqueErrors = {}; + for (let e of errors) { + e = dictionaryErrorToString(e); + uniqueErrors[e] = uniqueErrors.hasOwnProperty(e) ? uniqueErrors[e] + 1 : 1; } - for (const [match, subst] of overrides) { - if (error.includes(match)) { - error = subst; - break; + for (const e in uniqueErrors) { + const count = uniqueErrors[e]; + const div = document.createElement('p'); + if (count > 1) { + div.textContent = `${e} `; + const em = document.createElement('em'); + em.textContent = `(${count})`; + div.appendChild(em); + } else { + div.textContent = `${e}`; } + dialog.append($(div)); } - dialog.show().text(error); + dialog.show(); } else { dialog.hide(); } @@ -319,7 +346,7 @@ async function onDictionaryPurge(e) { const dictProgress = $('#dict-purge').show(); try { - dictionaryErrorShow(); + dictionaryErrorsShow(null); dictionarySpinnerShow(true); await utilDatabasePurge(); @@ -331,7 +358,7 @@ async function onDictionaryPurge(e) { await dictionaryGroupsPopulate(options); await formMainDictionaryOptionsPopulate(options); } catch (e) { - dictionaryErrorShow(e); + dictionaryErrorsShow([e]); } finally { dictionarySpinnerShow(false); @@ -346,25 +373,32 @@ async function onDictionaryImport(e) { const dictProgress = $('#dict-import-progress').show(); try { - dictionaryErrorShow(); + dictionaryErrorsShow(null); dictionarySpinnerShow(true); const setProgress = percent => dictProgress.find('.progress-bar').css('width', `${percent}%`); const updateProgress = (total, current) => setProgress(current / total * 100.0); setProgress(0.0); + const exceptions = []; const options = await optionsLoad(); - const summary = await utilDatabaseImport(e.target.files[0], updateProgress); + const summary = await utilDatabaseImport(e.target.files[0], updateProgress, exceptions); options.dictionaries[summary.title] = {enabled: true, priority: 0, allowSecondarySearches: false}; if (summary.sequenced && options.general.mainDictionary === '') { options.general.mainDictionary = summary.title; } + + if (exceptions.length > 0) { + exceptions.push(`Dictionary may not have been imported properly: ${exceptions.length} error${exceptions.length === 1 ? '' : 's'} reported.`); + dictionaryErrorsShow(exceptions); + } + await optionsSave(options); await dictionaryGroupsPopulate(options); await formMainDictionaryOptionsPopulate(options); } catch (e) { - dictionaryErrorShow(e); + dictionaryErrorsShow([e]); } finally { dictionarySpinnerShow(false); diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 216cef3f..34b06ddb 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -87,6 +87,6 @@ function utilDatabasePurge() { return utilBackend().translator.database.purge(); } -function utilDatabaseImport(data, progress) { - return utilBackend().translator.database.importDictionary(data, progress); +function utilDatabaseImport(data, progress, exceptions) { + return utilBackend().translator.database.importDictionary(data, progress, exceptions); } From f2a5d5095931a78dc388d4cba66953560a336e7f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 26 Feb 2019 21:03:34 -0500 Subject: [PATCH 8/8] Database changes automatically update storage stats --- ext/bg/js/settings.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 99463593..49bf264d 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -364,6 +364,10 @@ async function onDictionaryPurge(e) { dictControls.show(); dictProgress.hide(); + + if (storageEstimate.mostRecent !== null) { + storageUpdateStats(); + } } } @@ -377,7 +381,12 @@ async function onDictionaryImport(e) { dictionarySpinnerShow(true); const setProgress = percent => dictProgress.find('.progress-bar').css('width', `${percent}%`); - const updateProgress = (total, current) => setProgress(current / total * 100.0); + const updateProgress = (total, current) => { + setProgress(current / total * 100.0); + if (storageEstimate.mostRecent !== null && !storageUpdateStats.isUpdating) { + storageUpdateStats(); + } + }; setProgress(0.0); const exceptions = []; @@ -588,16 +597,17 @@ function storageBytesToLabeledString(size) { size /= base; ++labelIndex; } - const label = size.toFixed(1).replace(/\.0+$/, ""); + const label = size.toFixed(1); return `${label}${labels[labelIndex]}`; } async function storageEstimate() { try { - return await navigator.storage.estimate(); + return (storageEstimate.mostRecent = await navigator.storage.estimate()); } catch (e) { } return null; } +storageEstimate.mostRecent = null; async function storageInfoInitialize() { const browser = await getBrowser(); @@ -611,8 +621,8 @@ async function storageInfoInitialize() { document.querySelector("#storage-refresh").addEventListener('click', () => storageShowInfo(), false); } -async function storageShowInfo() { - storageSpinnerShow(true); +async function storageUpdateStats() { + storageUpdateStats.isUpdating = true; const estimate = await storageEstimate(); const valid = (estimate !== null); @@ -621,6 +631,16 @@ async function storageShowInfo() { document.querySelector("#storage-usage").textContent = storageBytesToLabeledString(estimate.usage); document.querySelector("#storage-quota").textContent = storageBytesToLabeledString(estimate.quota); } + + storageUpdateStats.isUpdating = false; + return valid; +} +storageUpdateStats.isUpdating = false; + +async function storageShowInfo() { + storageSpinnerShow(true); + + const valid = await storageUpdateStats(); document.querySelector("#storage-use").classList.toggle("storage-hidden", !valid); document.querySelector("#storage-error").classList.toggle("storage-hidden", valid);