Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for exporting and importing dictionaries database. #187

Merged
merged 1 commit into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ Yomitan provides advanced features not available in other browser-based dictiona
## Table of Contents

* [Installation](#installation)
* [Migrating from Yomichan](#migrating-from-yomichan)
* [Dictionaries](#dictionaries)
* [Basic Usage](#basic-usage)
* [Importing Dictionaries](#importing-dictionaries)
* [Custom Dictionaries](#custom-dictionaries)
* [Anki Integration](#anki-integration)
* [Flashcard Configuration](#flashcard-configuration)
Expand Down Expand Up @@ -65,6 +67,17 @@ submitting issues on GitHub, try the *testing* version; otherwise, the *stable*
You will have to download a desired version and side-load it yourself. You only need to do this once and will get
updates automatically.

## Migrating from Yomichan

If you are an existing user of Yomichan, you can export your dictionary collection and settings such that they can be imported into Yomitan to reflect your setup exactly as it was.

You can export your settings from Yomichan's Settings page. Go to the `Backup` section and click on `Export Settings`.

Yomichan doesn't have first-class support to export the dictionary collection. Please follow the instructions provided in the following link to export your data:
https://github.com/themoeway/yomichan-data-exporter#steps-to-export-the-data

You can them import the exported files into Yomitan from the `Backup` section of the `Settings` page. Please see [the section on importing dictionaries](#importing-dictionaries) further below for more explicit steps.

## Dictionaries

There are several free Japanese dictionaries available for Yomitan, with two of them having glossaries available in
Expand Down Expand Up @@ -99,6 +112,7 @@ language is not English, you may consider also importing the English version for
* **[Kanjium](https://github.com/mifunetoshiro/kanjium)** (Pitch dictionary, see [related project page](https://github.com/toasted-nutbread/yomichan-pitch-accent-dictionary) for details)
* [kanjium_pitch_accents.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjium_pitch_accents.zip)


## Basic Usage

1. Click the <img src="ext/images/yomitan-icon.svg" alt="" width="16" height="16"> _Yomitan_ button in the browser bar to open the quick-actions popup.
Expand Down Expand Up @@ -131,6 +145,32 @@ language is not English, you may consider also importing the English version for

<img src="resources/images/search-popup-kanji.png" alt="">

### Importing Dictionaries

You can import individual dictionaries from the settings page as described above.

Yomitan also supports exporting and importing your entire collection of dictionaries.

#### Importing a Dictionary Collection

- Go to Yomitan's Settings page (Click on the extension's icon then click on the cog icon from the popup)
- Click `Import Dictionary Collection` and select the database file you want to import
- Wait for the import to finish then turn all the dictionaries back on from the `Dictionaries > Configure installed and enabled dictionaries` section
- Refresh the browser tab to see the dictionaries in effect

#### Exporting the Dictionary Collection

- Click `Export Dictionary Collection` from the backup section of Yomitan's settings page
- It will show you a progress report as it exports the data then initiates a
download for a file named something like `yomitan-dictionaries-YYYY-MM-DD-HH-mm-ss.json`
(e.g. `yomitan-dictionaries-2023-07-05-02-42-04.json`)

### Importing and Exporting Personal Configuration

Note that you can also similarly export and import your Yomitan settings from the `Backup` section of the Settings page.

You should be able to replicate your exact Yomitan setup across devices by exporting your settings and dictionary collection from the source device then importing those from the destination.

## Custom Dictionaries

Yomitan supports the use of custom dictionaries, including the esoteric but popular
Expand Down
10 changes: 10 additions & 0 deletions ext/css/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,16 @@ select.short-height {
padding-top: var(--padding);
padding-right: var(--padding);
}
.settings-item-progress-report {
display: none;
font-weight: bold;
color: #4169e1;
}
.settings-item-error-report {
display: none;
font-weight: bold;
color: #8b0000;
}


/* Advanced settings */
Expand Down
130 changes: 130 additions & 0 deletions ext/js/pages/settings/backup-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

/* global
* ArrayBufferUtil
* Dexie
* DictionaryController
* OptionsUtil
*/
Expand All @@ -33,6 +34,10 @@ class BackupController {
this._settingsImportErrorModal = null;
this._settingsImportWarningModal = null;
this._optionsUtil = null;

this._dictionariesDatabaseName = 'dict';
this._settingsExportDatabaseToken = null;

try {
this._optionsUtil = new OptionsUtil();
} catch (e) {
Expand All @@ -56,6 +61,10 @@ class BackupController {
this._addNodeEventListener('#settings-import-file', 'change', this._onSettingsImportFileChange.bind(this), false);
this._addNodeEventListener('#settings-reset-button', 'click', this._onSettingsResetClick.bind(this), false);
this._addNodeEventListener('#settings-reset-confirm-button', 'click', this._onSettingsResetConfirmClick.bind(this), false);

this._addNodeEventListener('#settings-export-db-button', 'click', this._onSettingsExportDatabaseClick.bind(this), false);
this._addNodeEventListener('#settings-import-db-button', 'click', this._onSettingsImportDatabaseClick.bind(this), false);
this._addNodeEventListener('#settings-import-db', 'change', this._onSettingsImportDatabaseChange.bind(this), false);
}

// Private
Expand Down Expand Up @@ -413,4 +422,125 @@ class BackupController {
log.error(e);
}
}

// Exporting Dictionaries Database

_databaseExportImportErrorMessage(message, isWarning=false) {
const errorMessageContainer = document.querySelector('#db-ops-error-report');
errorMessageContainer.style.display = 'block';
errorMessageContainer.textContent = message;

if (isWarning) { // Hide after 5 seconds (5000 milliseconds)
errorMessageContainer.style.color = '#FFC40C';
setTimeout(function _hideWarningMessage() {
errorMessageContainer.style.display = 'none';
errorMessageContainer.style.color = '#8B0000';
}, 5000);
}
}

_databaseExportProgressCallback({totalRows, completedRows, done}) {
console.log(`Progress: ${completedRows} of ${totalRows} rows completed`);
const messageContainer = document.querySelector('#db-ops-progress-report');
messageContainer.style.display = 'block';
messageContainer.textContent = `Export Progress: ${completedRows} of ${totalRows} rows completed`;

if (done) {
console.log('Done exporting.');
messageContainer.style.display = 'none';
}
}

async _exportDatabase(databaseName) {
const db = await new Dexie(databaseName).open();
const blob = await db.export({progressCallback: this._databaseExportProgressCallback});
await db.close();
return blob;
}

async _onSettingsExportDatabaseClick() {
if (this._settingsExportDatabaseToken !== null) {
// An existing import or export is in progress.
this._databaseExportImportErrorMessage('An export or import operation is already in progress. Please wait till it is over.', true);
return;
}

const errorMessageContainer = document.querySelector('#db-ops-error-report');
errorMessageContainer.style.display = 'none';

const date = new Date(Date.now());
const pageExitPrevention = this._settingsController.preventPageExit();
try {
const token = {};
this._settingsExportDatabaseToken = token;
const fileName = `yomitan-dictionaries-${this._getSettingsExportDateString(date, '-', '-', '-', 6)}.json`;
const data = await this._exportDatabase(this._dictionariesDatabaseName);
const blob = new Blob([data], {type: 'application/json'});
this._saveBlob(blob, fileName);
} catch (error) {
console.log(error);
this._databaseExportImportErrorMessage('Errors encountered while exporting. Please try again. Restart the browser if it continues to fail.');
} finally {
pageExitPrevention.end();
this._settingsExportDatabaseToken = null;
}
}

// Importing Dictionaries Database

_databaseImportProgressCallback({totalRows, completedRows, done}) {
console.log(`Progress: ${completedRows} of ${totalRows} rows completed`);
const messageContainer = document.querySelector('#db-ops-progress-report');
messageContainer.style.display = 'block';
messageContainer.style.color = '#4169e1';
messageContainer.textContent = `Import Progress: ${completedRows} of ${totalRows} rows completed`;

if (done) {
console.log('Done importing.');
messageContainer.style.color = '#006633';
messageContainer.textContent = 'Done importing. You will need to re-enable the dictionaries and refresh afterward. If you run into issues, please restart the browser. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.';
}
}

async _importDatabase(databaseName, file) {
await yomichan.api.purgeDatabase();
await Dexie.import(file, {progressCallback: this._databaseImportProgressCallback});
yomichan.api.triggerDatabaseUpdated('dictionary', 'import');
yomichan.trigger('storageChanged');
}

_onSettingsImportDatabaseClick() {
document.querySelector('#settings-import-db').click();
}

async _onSettingsImportDatabaseChange(e) {
if (this._settingsExportDatabaseToken !== null) {
// An existing import or export is in progress.
this._databaseExportImportErrorMessage('An export or import operation is already in progress. Please wait till it is over.', true);
return;
}

const errorMessageContainer = document.querySelector('#db-ops-error-report');
errorMessageContainer.style.display = 'none';

const files = e.target.files;
if (files.length === 0) { return; }

const pageExitPrevention = this._settingsController.preventPageExit();
const file = files[0];
e.target.value = null;
try {
const token = {};
this._settingsExportDatabaseToken = token;
await this._importDatabase(this._dictionariesDatabaseName, file);
} catch (error) {
console.log(error);
const messageContainer = document.querySelector('#db-ops-progress-report');
messageContainer.style.color = 'red';
this._databaseExportImportErrorMessage('Encountered errors when importing. Please restart the browser and try again. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.');
} finally {
pageExitPrevention.end();
this._settingsExportDatabaseToken = null;
}
}
}
Loading
Loading