Add apiModifySettings (#501)
* Update getProfile/getProfileFromContext to store this.options in a variable * Add useSchema parameter to options getter functions * Add apiModifySettings * Use apiModifySettings instead of apiOptionsSet * Remove apiOptionsSet * Fix incorrect deleteCount check * Require explicit scope for options * Throw on invalid scope
This commit is contained in:
parent
021ccb5ac3
commit
bb2d9501af
@ -46,6 +46,7 @@
|
|||||||
<script src="/bg/js/translator.js"></script>
|
<script src="/bg/js/translator.js"></script>
|
||||||
<script src="/bg/js/util.js"></script>
|
<script src="/bg/js/util.js"></script>
|
||||||
<script src="/mixed/js/audio-system.js"></script>
|
<script src="/mixed/js/audio-system.js"></script>
|
||||||
|
<script src="/mixed/js/object-property-accessor.js"></script>
|
||||||
|
|
||||||
<script src="/bg/js/background-main.js"></script>
|
<script src="/bg/js/background-main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
* DictionaryImporter
|
* DictionaryImporter
|
||||||
* JsonSchema
|
* JsonSchema
|
||||||
* Mecab
|
* Mecab
|
||||||
|
* ObjectPropertyAccessor
|
||||||
* Translator
|
* Translator
|
||||||
* conditionsTestValue
|
* conditionsTestValue
|
||||||
* dictTermsSort
|
* dictTermsSort
|
||||||
@ -84,7 +85,6 @@ class Backend {
|
|||||||
['optionsSchemaGet', {handler: this._onApiOptionsSchemaGet.bind(this), async: false}],
|
['optionsSchemaGet', {handler: this._onApiOptionsSchemaGet.bind(this), async: false}],
|
||||||
['optionsGet', {handler: this._onApiOptionsGet.bind(this), async: false}],
|
['optionsGet', {handler: this._onApiOptionsGet.bind(this), async: false}],
|
||||||
['optionsGetFull', {handler: this._onApiOptionsGetFull.bind(this), async: false}],
|
['optionsGetFull', {handler: this._onApiOptionsGetFull.bind(this), async: false}],
|
||||||
['optionsSet', {handler: this._onApiOptionsSet.bind(this), async: true}],
|
|
||||||
['optionsSave', {handler: this._onApiOptionsSave.bind(this), async: true}],
|
['optionsSave', {handler: this._onApiOptionsSave.bind(this), async: true}],
|
||||||
['kanjiFind', {handler: this._onApiKanjiFind.bind(this), async: true}],
|
['kanjiFind', {handler: this._onApiKanjiFind.bind(this), async: true}],
|
||||||
['termsFind', {handler: this._onApiTermsFind.bind(this), async: true}],
|
['termsFind', {handler: this._onApiTermsFind.bind(this), async: true}],
|
||||||
@ -115,7 +115,8 @@ class Backend {
|
|||||||
['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}],
|
['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}],
|
||||||
['log', {handler: this._onApiLog.bind(this), async: false}],
|
['log', {handler: this._onApiLog.bind(this), async: false}],
|
||||||
['logIndicatorClear', {handler: this._onApiLogIndicatorClear.bind(this), async: false}],
|
['logIndicatorClear', {handler: this._onApiLogIndicatorClear.bind(this), async: false}],
|
||||||
['createActionPort', {handler: this._onApiCreateActionPort.bind(this), async: false}]
|
['createActionPort', {handler: this._onApiCreateActionPort.bind(this), async: false}],
|
||||||
|
['modifySettings', {handler: this._onApiModifySettings.bind(this), async: true}]
|
||||||
]);
|
]);
|
||||||
this._messageHandlersWithProgress = new Map([
|
this._messageHandlersWithProgress = new Map([
|
||||||
['importDictionaryArchive', {handler: this._onApiImportDictionaryArchive.bind(this), async: true}],
|
['importDictionaryArchive', {handler: this._onApiImportDictionaryArchive.bind(this), async: true}],
|
||||||
@ -258,8 +259,9 @@ class Backend {
|
|||||||
return this.optionsSchema;
|
return this.optionsSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFullOptions() {
|
getFullOptions(useSchema=false) {
|
||||||
return this.options;
|
const options = this.options;
|
||||||
|
return useSchema ? JsonSchema.createProxy(options, this.optionsSchema) : options;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFullOptions(options) {
|
setFullOptions(options) {
|
||||||
@ -271,21 +273,22 @@ class Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getOptions(optionsContext) {
|
getOptions(optionsContext, useSchema=false) {
|
||||||
return this.getProfile(optionsContext).options;
|
return this.getProfile(optionsContext, useSchema).options;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProfile(optionsContext) {
|
getProfile(optionsContext, useSchema=false) {
|
||||||
const profiles = this.options.profiles;
|
const options = this.getFullOptions(useSchema);
|
||||||
|
const profiles = options.profiles;
|
||||||
if (typeof optionsContext.index === 'number') {
|
if (typeof optionsContext.index === 'number') {
|
||||||
return profiles[optionsContext.index];
|
return profiles[optionsContext.index];
|
||||||
}
|
}
|
||||||
const profile = this.getProfileFromContext(optionsContext);
|
const profile = this.getProfileFromContext(options, optionsContext);
|
||||||
return profile !== null ? profile : this.options.profiles[this.options.profileCurrent];
|
return profile !== null ? profile : options.profiles[options.profileCurrent];
|
||||||
}
|
}
|
||||||
|
|
||||||
getProfileFromContext(optionsContext) {
|
getProfileFromContext(options, optionsContext) {
|
||||||
for (const profile of this.options.profiles) {
|
for (const profile of options.profiles) {
|
||||||
const conditionGroups = profile.conditionGroups;
|
const conditionGroups = profile.conditionGroups;
|
||||||
if (conditionGroups.length > 0 && Backend.testConditionGroups(conditionGroups, optionsContext)) {
|
if (conditionGroups.length > 0 && Backend.testConditionGroups(conditionGroups, optionsContext)) {
|
||||||
return profile;
|
return profile;
|
||||||
@ -413,46 +416,6 @@ class Backend {
|
|||||||
return this.getFullOptions();
|
return this.getFullOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onApiOptionsSet({changedOptions, optionsContext, source}) {
|
|
||||||
const options = this.getOptions(optionsContext);
|
|
||||||
|
|
||||||
function getValuePaths(obj) {
|
|
||||||
const valuePaths = [];
|
|
||||||
const nodes = [{obj, path: []}];
|
|
||||||
while (nodes.length > 0) {
|
|
||||||
const node = nodes.pop();
|
|
||||||
for (const key of Object.keys(node.obj)) {
|
|
||||||
const path = node.path.concat(key);
|
|
||||||
const obj2 = node.obj[key];
|
|
||||||
if (obj2 !== null && typeof obj2 === 'object') {
|
|
||||||
nodes.unshift({obj: obj2, path});
|
|
||||||
} else {
|
|
||||||
valuePaths.push([obj2, path]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return valuePaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
function modifyOption(path, value) {
|
|
||||||
let pivot = options;
|
|
||||||
for (const key of path.slice(0, -1)) {
|
|
||||||
if (!hasOwn(pivot, key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
pivot = pivot[key];
|
|
||||||
}
|
|
||||||
pivot[path[path.length - 1]] = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [value, path] of getValuePaths(changedOptions)) {
|
|
||||||
modifyOption(path, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this._onApiOptionsSave({source});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onApiOptionsSave({source}) {
|
async _onApiOptionsSave({source}) {
|
||||||
const options = this.getFullOptions();
|
const options = this.getFullOptions();
|
||||||
await optionsSave(options);
|
await optionsSave(options);
|
||||||
@ -829,6 +792,20 @@ class Backend {
|
|||||||
await this.database.deleteDictionary(dictionaryName, {rate: 1000}, onProgress);
|
await this.database.deleteDictionary(dictionaryName, {rate: 1000}, onProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _onApiModifySettings({targets, source}) {
|
||||||
|
const results = [];
|
||||||
|
for (const target of targets) {
|
||||||
|
try {
|
||||||
|
this._modifySetting(target);
|
||||||
|
results.push({result: true});
|
||||||
|
} catch (e) {
|
||||||
|
results.push({error: errorToJson(e)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this._onApiOptionsSave({source});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
// Command handlers
|
// Command handlers
|
||||||
|
|
||||||
_createActionListenerPort(port, sender, handlers) {
|
_createActionListenerPort(port, sender, handlers) {
|
||||||
@ -988,6 +965,63 @@ class Backend {
|
|||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
|
|
||||||
|
_getModifySettingObject(target) {
|
||||||
|
const scope = target.scope;
|
||||||
|
switch (scope) {
|
||||||
|
case 'profile':
|
||||||
|
if (!isObject(target.optionsContext)) { throw new Error('Invalid optionsContext'); }
|
||||||
|
return this.getOptions(target.optionsContext, true);
|
||||||
|
case 'global':
|
||||||
|
return this.getFullOptions(true);
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid scope: ${scope}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _modifySetting(target) {
|
||||||
|
const options = this._getModifySettingObject(target);
|
||||||
|
const accessor = new ObjectPropertyAccessor(options);
|
||||||
|
const action = target.action;
|
||||||
|
switch (action) {
|
||||||
|
case 'set':
|
||||||
|
{
|
||||||
|
const {path, value} = target;
|
||||||
|
if (typeof path !== 'string') { throw new Error('Invalid path'); }
|
||||||
|
accessor.set(ObjectPropertyAccessor.getPathArray(path), value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
{
|
||||||
|
const {path} = target;
|
||||||
|
if (typeof path !== 'string') { throw new Error('Invalid path'); }
|
||||||
|
accessor.delete(ObjectPropertyAccessor.getPathArray(path));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'swap':
|
||||||
|
{
|
||||||
|
const {path1, path2} = target;
|
||||||
|
if (typeof path1 !== 'string') { throw new Error('Invalid path1'); }
|
||||||
|
if (typeof path2 !== 'string') { throw new Error('Invalid path2'); }
|
||||||
|
accessor.swap(ObjectPropertyAccessor.getPathArray(path1), ObjectPropertyAccessor.getPathArray(path2));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'splice':
|
||||||
|
{
|
||||||
|
const {path, start, deleteCount, items} = target;
|
||||||
|
if (typeof path !== 'string') { throw new Error('Invalid path'); }
|
||||||
|
if (typeof start !== 'number' || Math.floor(start) !== start) { throw new Error('Invalid start'); }
|
||||||
|
if (typeof deleteCount !== 'number' || Math.floor(deleteCount) !== deleteCount) { throw new Error('Invalid deleteCount'); }
|
||||||
|
if (!Array.isArray(items)) { throw new Error('Invalid items'); }
|
||||||
|
const array = accessor.get(ObjectPropertyAccessor.getPathArray(path));
|
||||||
|
if (!Array.isArray(array)) { throw new Error('Invalid target type'); }
|
||||||
|
array.splice(start, deleteCount, ...items);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown action: ${action}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_validatePrivilegedMessageSender(sender) {
|
_validatePrivilegedMessageSender(sender) {
|
||||||
const url = sender.url;
|
const url = sender.url;
|
||||||
if (!(typeof url === 'string' && yomichan.isExtensionUrl(url))) {
|
if (!(typeof url === 'string' && yomichan.isExtensionUrl(url))) {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
/* global
|
/* global
|
||||||
* QueryParserGenerator
|
* QueryParserGenerator
|
||||||
* TextScanner
|
* TextScanner
|
||||||
* apiOptionsSet
|
* apiModifySettings
|
||||||
* apiTermsFind
|
* apiTermsFind
|
||||||
* apiTextParse
|
* apiTextParse
|
||||||
* docSentenceExtract
|
* docSentenceExtract
|
||||||
@ -72,8 +72,14 @@ class QueryParser extends TextScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onParserChange(e) {
|
onParserChange(e) {
|
||||||
const selectedParser = e.target.value;
|
const value = e.target.value;
|
||||||
apiOptionsSet({parsing: {selectedParser}}, this.getOptionsContext());
|
apiModifySettings([{
|
||||||
|
action: 'set',
|
||||||
|
path: 'parsing.selectedParser',
|
||||||
|
value,
|
||||||
|
scope: 'profile',
|
||||||
|
optionsContext: this.getOptionsContext()
|
||||||
|
}], 'search');
|
||||||
}
|
}
|
||||||
|
|
||||||
getMouseEventListeners() {
|
getMouseEventListeners() {
|
||||||
@ -92,8 +98,14 @@ class QueryParser extends TextScanner {
|
|||||||
refreshSelectedParser() {
|
refreshSelectedParser() {
|
||||||
if (this.parseResults.length > 0) {
|
if (this.parseResults.length > 0) {
|
||||||
if (!this.getParseResult()) {
|
if (!this.getParseResult()) {
|
||||||
const selectedParser = this.parseResults[0].id;
|
const value = this.parseResults[0].id;
|
||||||
apiOptionsSet({parsing: {selectedParser}}, this.getOptionsContext());
|
apiModifySettings([{
|
||||||
|
action: 'set',
|
||||||
|
path: 'parsing.selectedParser',
|
||||||
|
value,
|
||||||
|
scope: 'profile',
|
||||||
|
optionsContext: this.getOptionsContext()
|
||||||
|
}], 'search');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
* Display
|
* Display
|
||||||
* QueryParser
|
* QueryParser
|
||||||
* apiClipboardGet
|
* apiClipboardGet
|
||||||
* apiOptionsSet
|
* apiModifySettings
|
||||||
* apiTermsFind
|
* apiTermsFind
|
||||||
* wanakana
|
* wanakana
|
||||||
*/
|
*/
|
||||||
@ -252,13 +252,19 @@ class DisplaySearch extends Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onWanakanaEnableChange(e) {
|
onWanakanaEnableChange(e) {
|
||||||
const enableWanakana = e.target.checked;
|
const value = e.target.checked;
|
||||||
if (enableWanakana) {
|
if (value) {
|
||||||
wanakana.bind(this.query);
|
wanakana.bind(this.query);
|
||||||
} else {
|
} else {
|
||||||
wanakana.unbind(this.query);
|
wanakana.unbind(this.query);
|
||||||
}
|
}
|
||||||
apiOptionsSet({general: {enableWanakana}}, this.getOptionsContext());
|
apiModifySettings([{
|
||||||
|
action: 'set',
|
||||||
|
path: 'general.enableWanakana',
|
||||||
|
value,
|
||||||
|
scope: 'profile',
|
||||||
|
optionsContext: this.getOptionsContext()
|
||||||
|
}], 'search');
|
||||||
}
|
}
|
||||||
|
|
||||||
onClipboardMonitorEnableChange(e) {
|
onClipboardMonitorEnableChange(e) {
|
||||||
@ -268,7 +274,13 @@ class DisplaySearch extends Display {
|
|||||||
(granted) => {
|
(granted) => {
|
||||||
if (granted) {
|
if (granted) {
|
||||||
this.clipboardMonitor.start();
|
this.clipboardMonitor.start();
|
||||||
apiOptionsSet({general: {enableClipboardMonitor: true}}, this.getOptionsContext());
|
apiModifySettings([{
|
||||||
|
action: 'set',
|
||||||
|
path: 'general.enableClipboardMonitor',
|
||||||
|
value: true,
|
||||||
|
scope: 'profile',
|
||||||
|
optionsContext: this.getOptionsContext()
|
||||||
|
}], 'search');
|
||||||
} else {
|
} else {
|
||||||
e.target.checked = false;
|
e.target.checked = false;
|
||||||
}
|
}
|
||||||
@ -276,7 +288,13 @@ class DisplaySearch extends Display {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.clipboardMonitor.stop();
|
this.clipboardMonitor.stop();
|
||||||
apiOptionsSet({general: {enableClipboardMonitor: false}}, this.getOptionsContext());
|
apiModifySettings([{
|
||||||
|
action: 'set',
|
||||||
|
path: 'general.enableClipboardMonitor',
|
||||||
|
value: false,
|
||||||
|
scope: 'profile',
|
||||||
|
optionsContext: this.getOptionsContext()
|
||||||
|
}], 'search');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +28,6 @@ function apiOptionsGetFull() {
|
|||||||
return _apiInvoke('optionsGetFull');
|
return _apiInvoke('optionsGetFull');
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiOptionsSet(changedOptions, optionsContext, source) {
|
|
||||||
return _apiInvoke('optionsSet', {changedOptions, optionsContext, source});
|
|
||||||
}
|
|
||||||
|
|
||||||
function apiOptionsSave(source) {
|
function apiOptionsSave(source) {
|
||||||
return _apiInvoke('optionsSave', {source});
|
return _apiInvoke('optionsSave', {source});
|
||||||
}
|
}
|
||||||
@ -160,6 +156,10 @@ function apiDeleteDictionary(dictionaryName, onProgress) {
|
|||||||
return _apiInvokeWithProgress('deleteDictionary', {dictionaryName}, onProgress);
|
return _apiInvokeWithProgress('deleteDictionary', {dictionaryName}, onProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function apiModifySettings(targets, source) {
|
||||||
|
return _apiInvoke('modifySettings', {targets, source});
|
||||||
|
}
|
||||||
|
|
||||||
function _apiCreateActionPort(timeout=5000) {
|
function _apiCreateActionPort(timeout=5000) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
Loading…
Reference in New Issue
Block a user