Add support for deleting individual dictionaries
This commit is contained in:
parent
e355b83914
commit
e091c7ebe2
@ -165,6 +165,24 @@ input[type=checkbox].storage-button-checkbox {
|
|||||||
height: 320px;
|
height: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dict-delete-table {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.dict-delete-table>*:first-child {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
.dict-delete-table>*:nth-child(n+2) {
|
||||||
|
display: table-cell;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.dict-delete-table .progress {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
[data-show-for-browser],
|
[data-show-for-browser],
|
||||||
[data-show-for-operating-system] {
|
[data-show-for-operating-system] {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -56,6 +56,42 @@ class Database {
|
|||||||
await this.prepare();
|
await this.prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteDictionary(dictionaryName, onProgress, progressSettings) {
|
||||||
|
this.validate();
|
||||||
|
|
||||||
|
const targets = [
|
||||||
|
['dictionaries', 'title'],
|
||||||
|
['kanji', 'dictionary'],
|
||||||
|
['kanjiMeta', 'dictionary'],
|
||||||
|
['terms', 'dictionary'],
|
||||||
|
['termMeta', 'dictionary'],
|
||||||
|
['tagMeta', 'dictionary']
|
||||||
|
];
|
||||||
|
const promises = [];
|
||||||
|
const progressData = {
|
||||||
|
count: 0,
|
||||||
|
processed: 0,
|
||||||
|
storeCount: targets.length,
|
||||||
|
storesProcesed: 0
|
||||||
|
};
|
||||||
|
let progressRate = (typeof progressSettings === 'object' && progressSettings !== null ? progressSettings.rate : 0);
|
||||||
|
if (typeof progressRate !== 'number' || progressRate <= 0) {
|
||||||
|
progressRate = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = this.db.backendDB();
|
||||||
|
|
||||||
|
for (const [objectStoreName, index] of targets) {
|
||||||
|
const dbTransaction = db.transaction([objectStoreName], 'readwrite');
|
||||||
|
const dbObjectStore = dbTransaction.objectStore(objectStoreName);
|
||||||
|
const dbIndex = dbObjectStore.index(index);
|
||||||
|
const only = IDBKeyRange.only(dictionaryName);
|
||||||
|
promises.push(Database.deleteValues(dbObjectStore, dbIndex, only, onProgress, progressData, progressRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
async findTermsBulk(termList, titles) {
|
async findTermsBulk(termList, titles) {
|
||||||
this.validate();
|
this.validate();
|
||||||
|
|
||||||
@ -612,4 +648,72 @@ class Database {
|
|||||||
request.onsuccess = (e) => resolve(e.target.result);
|
request.onsuccess = (e) => resolve(e.target.result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getAllKeys(dbIndex, query) {
|
||||||
|
const fn = typeof dbIndex.getAllKeys === 'function' ? Database.getAllKeysFast : Database.getAllKeysUsingCursor;
|
||||||
|
return fn(dbIndex, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAllKeysFast(dbIndex, query) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = dbIndex.getAllKeys(query);
|
||||||
|
request.onerror = (e) => reject(e);
|
||||||
|
request.onsuccess = (e) => resolve(e.target.result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAllKeysUsingCursor(dbIndex, query) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const primaryKeys = [];
|
||||||
|
const request = dbIndex.openKeyCursor(query, 'next');
|
||||||
|
request.onerror = (e) => reject(e);
|
||||||
|
request.onsuccess = (e) => {
|
||||||
|
const cursor = e.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
primaryKeys.push(cursor.primaryKey);
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
resolve(primaryKeys);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteValues(dbObjectStore, dbIndex, query, onProgress, progressData, progressRate) {
|
||||||
|
const hasProgress = (typeof onProgress === 'function');
|
||||||
|
const count = await Database.getCount(dbIndex, query);
|
||||||
|
++progressData.storesProcesed;
|
||||||
|
progressData.count += count;
|
||||||
|
if (hasProgress) {
|
||||||
|
onProgress(progressData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onValueDeleted = (
|
||||||
|
hasProgress ?
|
||||||
|
() => {
|
||||||
|
const p = ++progressData.processed;
|
||||||
|
if ((p % progressRate) === 0 || p === progressData.count) {
|
||||||
|
onProgress(progressData);
|
||||||
|
}
|
||||||
|
} :
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
const primaryKeys = await Database.getAllKeys(dbIndex, query);
|
||||||
|
for (const key of primaryKeys) {
|
||||||
|
const promise = Database.deleteValue(dbObjectStore, key).then(onValueDeleted);
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
static deleteValue(dbObjectStore, key) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = dbObjectStore.delete(key);
|
||||||
|
request.onerror = (e) => reject(e);
|
||||||
|
request.onsuccess = () => resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ class SettingsDictionaryListUI {
|
|||||||
|
|
||||||
this.dictionaryEntries = [];
|
this.dictionaryEntries = [];
|
||||||
this.extra = null;
|
this.extra = null;
|
||||||
|
|
||||||
|
document.querySelector('#dict-delete-confirm').addEventListener('click', (e) => this.onDictionaryConfirmDelete(e), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDictionaries(dictionaries) {
|
setDictionaries(dictionaries) {
|
||||||
@ -126,6 +128,19 @@ class SettingsDictionaryListUI {
|
|||||||
save() {
|
save() {
|
||||||
// Overwrite
|
// Overwrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDictionaryConfirmDelete(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const n = document.querySelector('#dict-delete-modal');
|
||||||
|
const title = n.dataset.dict;
|
||||||
|
delete n.dataset.dict;
|
||||||
|
$(n).modal('hide');
|
||||||
|
|
||||||
|
const index = this.dictionaryEntries.findIndex(e => e.dictionaryInfo.title === title);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.dictionaryEntries[index].deleteDictionary();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsDictionaryEntryUI {
|
class SettingsDictionaryEntryUI {
|
||||||
@ -135,11 +150,13 @@ class SettingsDictionaryEntryUI {
|
|||||||
this.optionsDictionary = optionsDictionary;
|
this.optionsDictionary = optionsDictionary;
|
||||||
this.counts = null;
|
this.counts = null;
|
||||||
this.eventListeners = [];
|
this.eventListeners = [];
|
||||||
|
this.isDeleting = false;
|
||||||
|
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.enabledCheckbox = this.content.querySelector('.dict-enabled');
|
this.enabledCheckbox = this.content.querySelector('.dict-enabled');
|
||||||
this.allowSecondarySearchesCheckbox = this.content.querySelector('.dict-allow-secondary-searches');
|
this.allowSecondarySearchesCheckbox = this.content.querySelector('.dict-allow-secondary-searches');
|
||||||
this.priorityInput = this.content.querySelector('.dict-priority');
|
this.priorityInput = this.content.querySelector('.dict-priority');
|
||||||
|
this.deleteButton = this.content.querySelector('.dict-delete-button');
|
||||||
|
|
||||||
this.content.querySelector('.dict-title').textContent = this.dictionaryInfo.title;
|
this.content.querySelector('.dict-title').textContent = this.dictionaryInfo.title;
|
||||||
this.content.querySelector('.dict-revision').textContent = `rev.${this.dictionaryInfo.revision}`;
|
this.content.querySelector('.dict-revision').textContent = `rev.${this.dictionaryInfo.revision}`;
|
||||||
@ -149,6 +166,7 @@ class SettingsDictionaryEntryUI {
|
|||||||
this.addEventListener(this.enabledCheckbox, 'change', (e) => this.onEnabledChanged(e), false);
|
this.addEventListener(this.enabledCheckbox, 'change', (e) => this.onEnabledChanged(e), false);
|
||||||
this.addEventListener(this.allowSecondarySearchesCheckbox, 'change', (e) => this.onAllowSecondarySearchesChanged(e), false);
|
this.addEventListener(this.allowSecondarySearchesCheckbox, 'change', (e) => this.onAllowSecondarySearchesChanged(e), false);
|
||||||
this.addEventListener(this.priorityInput, 'change', (e) => this.onPriorityChanged(e), false);
|
this.addEventListener(this.priorityInput, 'change', (e) => this.onPriorityChanged(e), false);
|
||||||
|
this.addEventListener(this.deleteButton, 'click', (e) => this.onDeleteButtonClicked(e), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
@ -194,6 +212,38 @@ class SettingsDictionaryEntryUI {
|
|||||||
this.priorityInput.value = `${this.optionsDictionary.priority}`;
|
this.priorityInput.value = `${this.optionsDictionary.priority}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteDictionary() {
|
||||||
|
if (this.isDeleting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = this.content.querySelector('.progress');
|
||||||
|
progress.hidden = false;
|
||||||
|
const progressBar = this.content.querySelector('.progress-bar');
|
||||||
|
this.isDeleting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const onProgress = ({processed, count, storeCount, storesProcesed}) => {
|
||||||
|
let percent = 0.0;
|
||||||
|
if (count > 0 && storesProcesed > 0) {
|
||||||
|
percent = (processed / count) * (storesProcesed / storeCount) * 100.0;
|
||||||
|
}
|
||||||
|
progressBar.style.width = `${percent}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
await utilDatabaseDeleteDictionary(this.dictionaryInfo.title, onProgress, {rate: 1000});
|
||||||
|
} catch (e) {
|
||||||
|
dictionaryErrorsShow([e]);
|
||||||
|
} finally {
|
||||||
|
this.isDeleting = false;
|
||||||
|
progress.hidden = true;
|
||||||
|
|
||||||
|
const optionsContext = getOptionsContext();
|
||||||
|
const options = await apiOptionsGet(optionsContext);
|
||||||
|
onDatabaseUpdated(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onEnabledChanged(e) {
|
onEnabledChanged(e) {
|
||||||
this.optionsDictionary.enabled = !!e.target.checked;
|
this.optionsDictionary.enabled = !!e.target.checked;
|
||||||
this.save();
|
this.save();
|
||||||
@ -215,6 +265,20 @@ class SettingsDictionaryEntryUI {
|
|||||||
|
|
||||||
e.target.value = `${value}`;
|
e.target.value = `${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDeleteButtonClicked(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.isDeleting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = this.dictionaryInfo.title;
|
||||||
|
const n = document.querySelector('#dict-delete-modal');
|
||||||
|
n.dataset.dict = title;
|
||||||
|
document.querySelector('#dict-remove-modal-dict-name').textContent = title;
|
||||||
|
$(n).modal('show');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsDictionaryExtraUI {
|
class SettingsDictionaryExtraUI {
|
||||||
|
@ -42,6 +42,11 @@ class Translator {
|
|||||||
await this.database.purge();
|
await this.database.purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteDictionary(dictionaryName) {
|
||||||
|
this.tagCache = {};
|
||||||
|
await this.database.deleteDictionary(dictionaryName);
|
||||||
|
}
|
||||||
|
|
||||||
async findTermsGrouped(text, dictionaries, alphanumeric, options) {
|
async findTermsGrouped(text, dictionaries, alphanumeric, options) {
|
||||||
const titles = Object.keys(dictionaries);
|
const titles = Object.keys(dictionaries);
|
||||||
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
|
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
|
||||||
|
@ -100,6 +100,10 @@ function utilDatabasePurge() {
|
|||||||
return utilBackend().translator.purgeDatabase();
|
return utilBackend().translator.purgeDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function utilDatabaseDeleteDictionary(dictionaryName, onProgress) {
|
||||||
|
return utilBackend().translator.database.deleteDictionary(dictionaryName, onProgress);
|
||||||
|
}
|
||||||
|
|
||||||
async function utilDatabaseImport(data, progress, exceptions) {
|
async function utilDatabaseImport(data, progress, exceptions) {
|
||||||
// Edge cannot read data on the background page due to the File object
|
// Edge cannot read data on the background page due to the File object
|
||||||
// being created from a different window. Read on the same page instead.
|
// being created from a different window. Read on the same page instead.
|
||||||
|
@ -418,7 +418,6 @@
|
|||||||
|
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
Yomichan can import and use a variety of dictionary formats. Unneeded dictionaries can be disabled.
|
Yomichan can import and use a variety of dictionary formats. Unneeded dictionaries can be disabled.
|
||||||
Deleting individual dictionaries is not currently feasible due to limitations of browser database technology.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="form-group" id="dict-main-group">
|
<div class="form-group" id="dict-main-group">
|
||||||
@ -471,6 +470,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" tabindex="-1" role="dialog" id="dict-delete-modal">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title">Confirm dictionary deletion</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
Are you sure you want to delete the dictionary <em id="dict-remove-modal-dict-name"></em>?
|
||||||
|
This operation may take some time and the responsiveness of this browser tab may be reduced.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="dict-delete-confirm">Delete Dictionary</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template id="dict-template"><div class="dict-group well well-sm">
|
<template id="dict-template"><div class="dict-group well well-sm">
|
||||||
<h4><span class="text-muted glyphicon glyphicon-book"></span> <span class="dict-title"></span> <small class="dict-revision"></small></h4>
|
<h4><span class="text-muted glyphicon glyphicon-book"></span> <span class="dict-title"></span> <small class="dict-revision"></small></h4>
|
||||||
<p class="text-warning" hidden>This dictionary is outdated and may not support new extension features; please import the latest version.</p>
|
<p class="text-warning" hidden>This dictionary is outdated and may not support new extension features; please import the latest version.</p>
|
||||||
@ -485,6 +503,16 @@
|
|||||||
<label class="dict-result-priority-label">Result priority</label>
|
<label class="dict-result-priority-label">Result priority</label>
|
||||||
<input type="number" class="form-control dict-priority">
|
<input type="number" class="form-control dict-priority">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dict-delete-table">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-default dict-delete-button">Delete Dictionary</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="progress" hidden>
|
||||||
|
<div class="progress-bar progress-bar-striped" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<pre class="debug dict-counts" hidden></pre>
|
<pre class="debug dict-counts" hidden></pre>
|
||||||
</div></template>
|
</div></template>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user