OptionsUtil refactor / options default values (#807)
* Replace _readFile with _fetchAsset for consistency with Backend * Fix error messages * Make OptionsUtil non-static * Update how default options are assigned * Add createValidatingProxy * Add validate, update _onApiSetAllSettings * Remove unused api.optionsSchemaGet * Remove Backend._optionsSchema * Update OptionsUtil to create its own JsonSchemaValidator * Rename Backend._optionsSchemaValidator * Make getDefault non-async
This commit is contained in:
parent
a1729eb9ae
commit
f168efb69c
@ -41,8 +41,7 @@ class Backend {
|
|||||||
this._mecab = new Mecab();
|
this._mecab = new Mecab();
|
||||||
this._clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)});
|
this._clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)});
|
||||||
this._options = null;
|
this._options = null;
|
||||||
this._optionsSchema = null;
|
this._profileConditionsSchemaValidator = new JsonSchemaValidator();
|
||||||
this._optionsSchemaValidator = new JsonSchemaValidator();
|
|
||||||
this._profileConditionsSchemaCache = [];
|
this._profileConditionsSchemaCache = [];
|
||||||
this._profileConditionsUtil = new ProfileConditions();
|
this._profileConditionsUtil = new ProfileConditions();
|
||||||
this._defaultAnkiFieldTemplates = null;
|
this._defaultAnkiFieldTemplates = null;
|
||||||
@ -55,6 +54,7 @@ class Backend {
|
|||||||
requestBuilder: this._requestBuilder,
|
requestBuilder: this._requestBuilder,
|
||||||
useCache: false
|
useCache: false
|
||||||
});
|
});
|
||||||
|
this._optionsUtil = new OptionsUtil();
|
||||||
|
|
||||||
this._clipboardPasteTarget = null;
|
this._clipboardPasteTarget = null;
|
||||||
this._clipboardPasteTargetInitialized = false;
|
this._clipboardPasteTargetInitialized = false;
|
||||||
@ -78,7 +78,6 @@ class Backend {
|
|||||||
|
|
||||||
this._messageHandlers = new Map([
|
this._messageHandlers = new Map([
|
||||||
['requestBackendReadySignal', {async: false, contentScript: true, handler: this._onApiRequestBackendReadySignal.bind(this)}],
|
['requestBackendReadySignal', {async: false, contentScript: true, handler: this._onApiRequestBackendReadySignal.bind(this)}],
|
||||||
['optionsSchemaGet', {async: false, contentScript: true, handler: this._onApiOptionsSchemaGet.bind(this)}],
|
|
||||||
['optionsGet', {async: false, contentScript: true, handler: this._onApiOptionsGet.bind(this)}],
|
['optionsGet', {async: false, contentScript: true, handler: this._onApiOptionsGet.bind(this)}],
|
||||||
['optionsGetFull', {async: false, contentScript: true, handler: this._onApiOptionsGetFull.bind(this)}],
|
['optionsGetFull', {async: false, contentScript: true, handler: this._onApiOptionsGetFull.bind(this)}],
|
||||||
['optionsSave', {async: true, contentScript: true, handler: this._onApiOptionsSave.bind(this)}],
|
['optionsSave', {async: true, contentScript: true, handler: this._onApiOptionsSave.bind(this)}],
|
||||||
@ -188,10 +187,9 @@ class Backend {
|
|||||||
}
|
}
|
||||||
await this._translator.prepare();
|
await this._translator.prepare();
|
||||||
|
|
||||||
this._optionsSchema = await this._fetchAsset('/bg/data/options-schema.json', true);
|
await this._optionsUtil.prepare();
|
||||||
this._defaultAnkiFieldTemplates = (await this._fetchAsset('/bg/data/default-anki-field-templates.handlebars')).trim();
|
this._defaultAnkiFieldTemplates = (await this._fetchAsset('/bg/data/default-anki-field-templates.handlebars')).trim();
|
||||||
this._options = await OptionsUtil.load();
|
this._options = await this._optionsUtil.load();
|
||||||
this._options = this._optionsSchemaValidator.getValidValueOrDefault(this._optionsSchema, this._options);
|
|
||||||
|
|
||||||
this._applyOptions('background');
|
this._applyOptions('background');
|
||||||
|
|
||||||
@ -222,7 +220,7 @@ class Backend {
|
|||||||
|
|
||||||
getFullOptions(useSchema=false) {
|
getFullOptions(useSchema=false) {
|
||||||
const options = this._options;
|
const options = this._options;
|
||||||
return useSchema ? this._optionsSchemaValidator.createProxy(options, this._optionsSchema) : options;
|
return useSchema ? this._optionsUtil.createValidatingProxy(options) : options;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOptions(optionsContext, useSchema=false) {
|
getOptions(optionsContext, useSchema=false) {
|
||||||
@ -370,10 +368,6 @@ class Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onApiOptionsSchemaGet() {
|
|
||||||
return this._optionsSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onApiOptionsGet({optionsContext}) {
|
_onApiOptionsGet({optionsContext}) {
|
||||||
return this.getOptions(optionsContext);
|
return this.getOptions(optionsContext);
|
||||||
}
|
}
|
||||||
@ -385,7 +379,7 @@ class Backend {
|
|||||||
async _onApiOptionsSave({source}) {
|
async _onApiOptionsSave({source}) {
|
||||||
this._clearProfileConditionsSchemaCache();
|
this._clearProfileConditionsSchemaCache();
|
||||||
const options = this.getFullOptions();
|
const options = this.getFullOptions();
|
||||||
await OptionsUtil.save(options);
|
await this._optionsUtil.save(options);
|
||||||
this._applyOptions(source);
|
this._applyOptions(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,7 +781,8 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onApiSetAllSettings({value, source}) {
|
async _onApiSetAllSettings({value, source}) {
|
||||||
this._options = this._optionsSchemaValidator.getValidValueOrDefault(this._optionsSchema, value);
|
this._optionsUtil.validate(value);
|
||||||
|
this._options = clone(value);
|
||||||
await this._onApiOptionsSave({source});
|
await this._onApiOptionsSave({source});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1014,7 +1009,7 @@ class Backend {
|
|||||||
this._profileConditionsSchemaCache.push(schema);
|
this._profileConditionsSchemaCache.push(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conditionGroups.length > 0 && this._optionsSchemaValidator.isValid(optionsContext, schema)) {
|
if (conditionGroups.length > 0 && this._profileConditionsSchemaValidator.isValid(optionsContext, schema)) {
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
++index;
|
++index;
|
||||||
@ -1025,7 +1020,7 @@ class Backend {
|
|||||||
|
|
||||||
_clearProfileConditionsSchemaCache() {
|
_clearProfileConditionsSchemaCache() {
|
||||||
this._profileConditionsSchemaCache = [];
|
this._profileConditionsSchemaCache = [];
|
||||||
this._optionsSchemaValidator.clearCache();
|
this._profileConditionsSchemaValidator.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkLastError() {
|
_checkLastError() {
|
||||||
|
@ -149,7 +149,7 @@ class JsonSchemaValidator {
|
|||||||
getValidValueOrDefault(schema, value) {
|
getValidValueOrDefault(schema, value) {
|
||||||
let type = this._getValueType(value);
|
let type = this._getValueType(value);
|
||||||
const schemaType = schema.type;
|
const schemaType = schema.type;
|
||||||
if (!this._isValueTypeAny(value, type, schemaType)) {
|
if (typeof value === 'undefined' || !this._isValueTypeAny(value, type, schemaType)) {
|
||||||
let assignDefault = true;
|
let assignDefault = true;
|
||||||
|
|
||||||
const schemaDefault = schema.default;
|
const schemaDefault = schema.default;
|
||||||
|
@ -15,8 +15,21 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* global
|
||||||
|
* JsonSchemaValidator
|
||||||
|
*/
|
||||||
|
|
||||||
class OptionsUtil {
|
class OptionsUtil {
|
||||||
static async update(options) {
|
constructor() {
|
||||||
|
this._schemaValidator = new JsonSchemaValidator();
|
||||||
|
this._optionsSchema = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepare() {
|
||||||
|
this._optionsSchema = await this._fetchAsset('/bg/data/options-schema.json', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(options) {
|
||||||
// Invalid options
|
// Invalid options
|
||||||
if (!isObject(options)) {
|
if (!isObject(options)) {
|
||||||
options = {};
|
options = {};
|
||||||
@ -72,14 +85,14 @@ class OptionsUtil {
|
|||||||
return await this._applyUpdates(options, this._getVersionUpdates());
|
return await this._applyUpdates(options, this._getVersionUpdates());
|
||||||
}
|
}
|
||||||
|
|
||||||
static async load() {
|
async load() {
|
||||||
let options = null;
|
let options;
|
||||||
try {
|
try {
|
||||||
const optionsStr = await new Promise((resolve, reject) => {
|
const optionsStr = await new Promise((resolve, reject) => {
|
||||||
chrome.storage.local.get(['options'], (store) => {
|
chrome.storage.local.get(['options'], (store) => {
|
||||||
const error = chrome.runtime.lastError;
|
const error = chrome.runtime.lastError;
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(new Error(error));
|
reject(new Error(error.message));
|
||||||
} else {
|
} else {
|
||||||
resolve(store.options);
|
resolve(store.options);
|
||||||
}
|
}
|
||||||
@ -90,15 +103,21 @@ class OptionsUtil {
|
|||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.update(options);
|
if (typeof options !== 'undefined') {
|
||||||
|
options = await this.update(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = this._schemaValidator.getValidValueOrDefault(this._optionsSchema, options);
|
||||||
|
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static save(options) {
|
save(options) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
chrome.storage.local.set({options: JSON.stringify(options)}, () => {
|
chrome.storage.local.set({options: JSON.stringify(options)}, () => {
|
||||||
const error = chrome.runtime.lastError;
|
const error = chrome.runtime.lastError;
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(new Error(error));
|
reject(new Error(error.message));
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@ -106,13 +125,21 @@ class OptionsUtil {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getDefault() {
|
getDefault() {
|
||||||
return await this.update({});
|
return this._schemaValidator.getValidValueOrDefault(this._optionsSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
createValidatingProxy(options) {
|
||||||
|
return this._schemaValidator.createProxy(options, this._optionsSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(options) {
|
||||||
|
return this._schemaValidator.validate(options, this._optionsSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy profile updating
|
// Legacy profile updating
|
||||||
|
|
||||||
static _legacyProfileUpdateGetUpdates() {
|
_legacyProfileUpdateGetUpdates() {
|
||||||
return [
|
return [
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -203,7 +230,7 @@ class OptionsUtil {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static _legacyProfileUpdateGetDefaults() {
|
_legacyProfileUpdateGetDefaults() {
|
||||||
return {
|
return {
|
||||||
general: {
|
general: {
|
||||||
enable: true,
|
enable: true,
|
||||||
@ -302,7 +329,7 @@ class OptionsUtil {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static _legacyProfileUpdateAssignDefaults(options) {
|
_legacyProfileUpdateAssignDefaults(options) {
|
||||||
const defaults = this._legacyProfileUpdateGetDefaults();
|
const defaults = this._legacyProfileUpdateGetDefaults();
|
||||||
|
|
||||||
const combine = (target, source) => {
|
const combine = (target, source) => {
|
||||||
@ -323,7 +350,7 @@ class OptionsUtil {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _legacyProfileUpdateUpdateVersion(options) {
|
_legacyProfileUpdateUpdateVersion(options) {
|
||||||
const updates = this._legacyProfileUpdateGetUpdates();
|
const updates = this._legacyProfileUpdateGetUpdates();
|
||||||
this._legacyProfileUpdateAssignDefaults(options);
|
this._legacyProfileUpdateAssignDefaults(options);
|
||||||
|
|
||||||
@ -345,20 +372,20 @@ class OptionsUtil {
|
|||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
static async _addFieldTemplatesToOptions(options, additionSourceUrl) {
|
async _addFieldTemplatesToOptions(options, additionSourceUrl) {
|
||||||
let addition = null;
|
let addition = null;
|
||||||
for (const {options: profileOptions} of options.profiles) {
|
for (const {options: profileOptions} of options.profiles) {
|
||||||
const fieldTemplates = profileOptions.anki.fieldTemplates;
|
const fieldTemplates = profileOptions.anki.fieldTemplates;
|
||||||
if (fieldTemplates !== null) {
|
if (fieldTemplates !== null) {
|
||||||
if (addition === null) {
|
if (addition === null) {
|
||||||
addition = await this._readFile(additionSourceUrl);
|
addition = await this._fetchAsset(additionSourceUrl);
|
||||||
}
|
}
|
||||||
profileOptions.anki.fieldTemplates = this._addFieldTemplatesBeforeEnd(fieldTemplates, addition);
|
profileOptions.anki.fieldTemplates = this._addFieldTemplatesBeforeEnd(fieldTemplates, addition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async _addFieldTemplatesBeforeEnd(fieldTemplates, addition) {
|
async _addFieldTemplatesBeforeEnd(fieldTemplates, addition) {
|
||||||
const pattern = /[ \t]*\{\{~?>\s*\(\s*lookup\s*\.\s*"marker"\s*\)\s*~?\}\}/;
|
const pattern = /[ \t]*\{\{~?>\s*\(\s*lookup\s*\.\s*"marker"\s*\)\s*~?\}\}/;
|
||||||
const newline = '\n';
|
const newline = '\n';
|
||||||
let replaced = false;
|
let replaced = false;
|
||||||
@ -373,7 +400,7 @@ class OptionsUtil {
|
|||||||
return fieldTemplates;
|
return fieldTemplates;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async _readFile(url) {
|
async _fetchAsset(url, json=false) {
|
||||||
url = chrome.runtime.getURL(url);
|
url = chrome.runtime.getURL(url);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -383,10 +410,13 @@ class OptionsUtil {
|
|||||||
redirect: 'follow',
|
redirect: 'follow',
|
||||||
referrerPolicy: 'no-referrer'
|
referrerPolicy: 'no-referrer'
|
||||||
});
|
});
|
||||||
return await response.text();
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch ${url}: ${response.status}`);
|
||||||
|
}
|
||||||
|
return await (json ? response.json() : response.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
static _getStringHashCode(string) {
|
_getStringHashCode(string) {
|
||||||
let hashCode = 0;
|
let hashCode = 0;
|
||||||
|
|
||||||
if (typeof string !== 'string') { return hashCode; }
|
if (typeof string !== 'string') { return hashCode; }
|
||||||
@ -399,7 +429,7 @@ class OptionsUtil {
|
|||||||
return hashCode;
|
return hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async _applyUpdates(options, updates) {
|
async _applyUpdates(options, updates) {
|
||||||
const targetVersion = updates.length;
|
const targetVersion = updates.length;
|
||||||
let currentVersion = options.version;
|
let currentVersion = options.version;
|
||||||
|
|
||||||
@ -417,7 +447,7 @@ class OptionsUtil {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _getVersionUpdates() {
|
_getVersionUpdates() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
async: false,
|
async: false,
|
||||||
@ -438,7 +468,7 @@ class OptionsUtil {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static _updateVersion1(options) {
|
_updateVersion1(options) {
|
||||||
// Version 1 changes:
|
// Version 1 changes:
|
||||||
// Added options.global.database.prefixWildcardsSupported = false.
|
// Added options.global.database.prefixWildcardsSupported = false.
|
||||||
options.global = {
|
options.global = {
|
||||||
@ -449,7 +479,7 @@ class OptionsUtil {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _updateVersion2(options) {
|
_updateVersion2(options) {
|
||||||
// Version 2 changes:
|
// Version 2 changes:
|
||||||
// Legacy profile update process moved into this upgrade function.
|
// Legacy profile update process moved into this upgrade function.
|
||||||
for (const profile of options.profiles) {
|
for (const profile of options.profiles) {
|
||||||
@ -461,14 +491,14 @@ class OptionsUtil {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async _updateVersion3(options) {
|
async _updateVersion3(options) {
|
||||||
// Version 3 changes:
|
// Version 3 changes:
|
||||||
// Pitch accent Anki field templates added.
|
// Pitch accent Anki field templates added.
|
||||||
await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v2.handlebars');
|
await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v2.handlebars');
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async _updateVersion4(options) {
|
async _updateVersion4(options) {
|
||||||
// Version 4 changes:
|
// Version 4 changes:
|
||||||
// Options conditions converted to string representations.
|
// Options conditions converted to string representations.
|
||||||
// Added usePopupWindow.
|
// Added usePopupWindow.
|
||||||
|
@ -26,9 +26,12 @@ class BackupController {
|
|||||||
this._settingsExportToken = null;
|
this._settingsExportToken = null;
|
||||||
this._settingsExportRevoke = null;
|
this._settingsExportRevoke = null;
|
||||||
this._currentVersion = 0;
|
this._currentVersion = 0;
|
||||||
|
this._optionsUtil = new OptionsUtil();
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
async prepare() {
|
||||||
|
await this._optionsUtil.prepare();
|
||||||
|
|
||||||
document.querySelector('#settings-export').addEventListener('click', this._onSettingsExportClick.bind(this), false);
|
document.querySelector('#settings-export').addEventListener('click', this._onSettingsExportClick.bind(this), false);
|
||||||
document.querySelector('#settings-import').addEventListener('click', this._onSettingsImportClick.bind(this), false);
|
document.querySelector('#settings-import').addEventListener('click', this._onSettingsImportClick.bind(this), false);
|
||||||
document.querySelector('#settings-import-file').addEventListener('change', this._onSettingsImportFileChange.bind(this), false);
|
document.querySelector('#settings-import-file').addEventListener('change', this._onSettingsImportFileChange.bind(this), false);
|
||||||
@ -140,7 +143,11 @@ class BackupController {
|
|||||||
// Importing
|
// Importing
|
||||||
|
|
||||||
async _settingsImportSetOptionsFull(optionsFull) {
|
async _settingsImportSetOptionsFull(optionsFull) {
|
||||||
await this._settingsController.setAllSettings(optionsFull);
|
try {
|
||||||
|
await this._settingsController.setAllSettings(optionsFull);
|
||||||
|
} catch (e) {
|
||||||
|
yomichan.logError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_showSettingsImportError(error) {
|
_showSettingsImportError(error) {
|
||||||
@ -322,7 +329,7 @@ class BackupController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade options
|
// Upgrade options
|
||||||
optionsFull = await OptionsUtil.update(optionsFull);
|
optionsFull = await this._optionsUtil.update(optionsFull);
|
||||||
|
|
||||||
// Check for warnings
|
// Check for warnings
|
||||||
const sanitizationWarnings = this._settingsImportSanitizeOptions(optionsFull, true);
|
const sanitizationWarnings = this._settingsImportSanitizeOptions(optionsFull, true);
|
||||||
@ -368,7 +375,7 @@ class BackupController {
|
|||||||
$('#settings-reset-modal').modal('hide');
|
$('#settings-reset-modal').modal('hide');
|
||||||
|
|
||||||
// Get default options
|
// Get default options
|
||||||
const optionsFull = await OptionsUtil.getDefault();
|
const optionsFull = this._optionsUtil.getDefault();
|
||||||
|
|
||||||
// Assign options
|
// Assign options
|
||||||
await this._settingsImportSetOptionsFull(optionsFull);
|
await this._settingsImportSetOptionsFull(optionsFull);
|
||||||
|
@ -49,10 +49,6 @@ const api = (() => {
|
|||||||
|
|
||||||
// Invoke functions
|
// Invoke functions
|
||||||
|
|
||||||
optionsSchemaGet() {
|
|
||||||
return this._invoke('optionsSchemaGet');
|
|
||||||
}
|
|
||||||
|
|
||||||
optionsGet(optionsContext) {
|
optionsGet(optionsContext) {
|
||||||
return this._invoke('optionsGet', {optionsContext});
|
return this._invoke('optionsGet', {optionsContext});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user