Skip to content

Commit

Permalink
Add recommended dictionaries to welcome page (#991)
Browse files Browse the repository at this point in the history
* Add dictionary import to welcome page

* Initial prototype of recommended dict importing

* Disable button immediately

* Add queue to allow clicking multiple import buttons at once

* Dynamic assignment of recommended dictionaries

* Add to json.json

* Change kanjidic to grab latest release

* Make linter happy

* Add json schema

* Add explicit types to dict array properties

* Fix schema $id

* Change types file to dts and add labeled test type

* Use static jitendex url

* Fix url path

* Remove BCCWJ-LUW version from name

* Only render recommended dictionaries on button click

* Clean up dictionary rendering

* Hide dictionaries when going from a language supporting recommended dictionaries to one that doesnt

* Update welcome page dict modals

* Fix spacing

* Add back recommended dictionary modals

* Fix dictionary importing

* Remove unneeded progress selectors

* Fix recommended dictionary modal rendering

* Add pronunciation to recommended dictionary types

* Fix KANJIDIC name

* Fix hiding and unhiding of elements

* Move language select above recommended dictionaries

* Use BCCWJ combined instead of BCCWJ-LUW

* show download progress

---------

Signed-off-by: Kuuuube <61125188+Kuuuube@users.noreply.github.com>
Co-authored-by: Stefan Vukovic <stefanvukovic44@gmail.com>
  • Loading branch information
Kuuuube and StefanVukovic99 authored Jul 29, 2024
1 parent b602851 commit 7dbced4
Show file tree
Hide file tree
Showing 8 changed files with 508 additions and 17 deletions.
15 changes: 15 additions & 0 deletions ext/data/recommended-dictionaries.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"ja": {
"terms": [
{"name": "Jitendex", "url": "https://github.com/stephenmk/stephenmk.github.io/releases/latest/download/jitendex-yomitan.zip"}
],
"kanji": [
{"name": "KANJIDIC", "url": "https://github.com/themoeway/jmdict-yomitan/releases/latest/download/KANJIDIC_english.zip"}
],
"frequency": [
{"name": "BCCWJ", "url": "https://github.com/Kuuuube/yomitan-dictionaries/releases/download/yomitan-permalink/BCCWJ_SUW_LUW_combined.zip"}
],
"grammar": [],
"pronunciation": []
}
}
120 changes: 120 additions & 0 deletions ext/data/schemas/recommended-dictionaries-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"$id": "recommendedDictionaries",
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Contains data for recommended dictionaries on welcome page.",
"type": "object",
"patternProperties": {
"^.{2,}$": {
"type": "object",
"properties": {
"terms": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"url"
],
"properties": {
"name": {
"type": "string",
"minLength": 2
},
"url": {
"type": "string",
"minLength": 2
}
}
}
},
"kanji": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"url"
],
"properties": {
"name": {
"type": "string",
"minLength": 2
},
"url": {
"type": "string",
"minLength": 2
}
}
}
},
"frequency": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"url"
],
"properties": {
"name": {
"type": "string",
"minLength": 2
},
"url": {
"type": "string",
"minLength": 2
}
}
}
},
"grammar": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"url"
],
"properties": {
"name": {
"type": "string",
"minLength": 2
},
"url": {
"type": "string",
"minLength": 2
}
}
}
},
"pronunciation": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"url"
],
"properties": {
"name": {
"type": "string",
"minLength": 2
},
"url": {
"type": "string",
"minLength": 2
}
}
}
}
},
"required": [
"terms",
"kanji",
"frequency",
"grammar"
],
"additionalProperties": false
}
}
}
127 changes: 125 additions & 2 deletions ext/js/pages/settings/dictionary-import-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import {ExtensionError} from '../../core/extension-error.js';
import {readResponseJson} from '../../core/json.js';
import {log} from '../../core/log.js';
import {toError} from '../../core/to-error.js';
import {DictionaryWorker} from '../../dictionary/dictionary-worker.js';
Expand Down Expand Up @@ -69,6 +70,10 @@ export class DictionaryImportController {
'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.',
],
];
/** @type {string[]} */
this._recommendedDictionaryQueue = [];
/** @type {boolean} */
this._recommendedDictionaryActiveImport = false;
}

/** */
Expand All @@ -89,10 +94,127 @@ export class DictionaryImportController {
this._importFileDrop.addEventListener('drop', this._onFileDrop.bind(this), false);

this._settingsController.on('importDictionaryFromUrl', this._onEventImportDictionaryFromUrl.bind(this));

// Welcome page
const recommendedDictionaryButton = document.querySelector('[data-modal-action="show,recommended-dictionaries"]');
if (recommendedDictionaryButton) {
recommendedDictionaryButton.addEventListener('click', this._renderRecommendedDictionaries.bind(this), false);
}
}

// Private

/**
* @param {MouseEvent} e
*/
async _onRecommendedImportClick(e) {
if (!(e instanceof PointerEvent)) { return; }
if (!e.target || !(e.target instanceof HTMLButtonElement)) { return; }

const import_url = e.target.attributes.getNamedItem('data-import-url');
if (!import_url) { return; }
this._recommendedDictionaryQueue.push(import_url.value);

e.target.disabled = true;

if (this._recommendedDictionaryActiveImport) { return; }

while (this._recommendedDictionaryQueue.length > 0) {
this._recommendedDictionaryActiveImport = true;
try {
const url = this._recommendedDictionaryQueue.shift();
if (!url) { continue; }

const importProgressTracker = new ImportProgressTracker(this._getUrlImportSteps(), 1);
const onProgress = importProgressTracker.onProgress.bind(importProgressTracker);
void this._importDictionaries(
this._generateFilesFromUrls([url], onProgress),
importProgressTracker,
);
} catch (error) {
log.error(error);
}
}
this._recommendedDictionaryActiveImport = false;
}

/** */
async _renderRecommendedDictionaries() {
const url = '../../data/recommended-dictionaries.json';
const response = await fetch(url, {
method: 'GET',
mode: 'no-cors',
cache: 'default',
credentials: 'omit',
redirect: 'follow',
referrerPolicy: 'no-referrer',
});
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.status}`);
}

/** @type {import('dictionary-recommended.js').RecommendedDictionaryElementMap[]} */
const recommendedDictionaryCategories = [
{property: 'terms', element: querySelectorNotNull(querySelectorNotNull(document, '#recommended-term-dictionaries'), '.recommended-dictionary-list')},
{property: 'kanji', element: querySelectorNotNull(querySelectorNotNull(document, '#recommended-kanji-dictionaries'), '.recommended-dictionary-list')},
{property: 'frequency', element: querySelectorNotNull(querySelectorNotNull(document, '#recommended-frequency-dictionaries'), '.recommended-dictionary-list')},
{property: 'grammar', element: querySelectorNotNull(querySelectorNotNull(document, '#recommended-grammar-dictionaries'), '.recommended-dictionary-list')},
{property: 'pronunciation', element: querySelectorNotNull(querySelectorNotNull(document, '#recommended-pronunciation-dictionaries'), '.recommended-dictionary-list')},
];

const language = (await this._settingsController.getOptions()).general.language;
/** @type {import('dictionary-recommended.js').RecommendedDictionaries} */
const recommendedDictionaries = (await readResponseJson(response));

if (!(language in recommendedDictionaries)) {
for (const {element} of recommendedDictionaryCategories) {
const dictionaryCategoryParent = element.parentElement;
if (dictionaryCategoryParent) {
dictionaryCategoryParent.hidden = true;
}
}
return;
}

for (const {property, element} of recommendedDictionaryCategories) {
this._renderRecommendedDictionaryGroup(recommendedDictionaries[language][property], element);
}

/** @type {NodeListOf<HTMLElement>} */
const buttons = document.querySelectorAll('.action-button[data-action=import-recommended-dictionary]');
for (const button of buttons) {
button.addEventListener('click', this._onRecommendedImportClick.bind(this), false);
}
}

/**
*
* @param {import('dictionary-recommended.js').Dictionary[]} recommendedDictionaries
* @param {HTMLElement} dictionariesList
*/
_renderRecommendedDictionaryGroup(recommendedDictionaries, dictionariesList) {
const dictionariesListParent = dictionariesList.parentElement;
dictionariesList.innerHTML = '';
for (const dictionary of recommendedDictionaries) {
if (dictionariesList) {
if (dictionariesListParent) {
dictionariesListParent.hidden = false;
}
const template = this._settingsController.instantiateTemplate('recommended-dictionaries-list-item');
const label = querySelectorNotNull(template, '.settings-item-label');
const button = querySelectorNotNull(template, '.action-button[data-action=import-recommended-dictionary]');

const urlAttribute = document.createAttribute('data-import-url');
urlAttribute.value = dictionary.url;
button.attributes.setNamedItem(urlAttribute);

label.textContent = dictionary.name;

dictionariesList.append(template);
}
}
}

/**
* @param {import('settings-controller').EventArgument<'importDictionaryFromUrl'>} details
*/
Expand Down Expand Up @@ -371,6 +493,7 @@ export class DictionaryImportController {
const statusFooter = this._statusFooter;
const progressSelector = '.dictionary-import-progress';
const progressContainers = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`#dictionaries-modal ${progressSelector}`));
const recommendedProgressContainers = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`#recommended-dictionaries-modal ${progressSelector}`));

const prevention = this._preventPageExit();

Expand All @@ -382,7 +505,7 @@ export class DictionaryImportController {
this._setModifying(true);
this._hideErrors();

for (const progress of progressContainers) { progress.hidden = false; }
for (const progress of [...progressContainers, ...recommendedProgressContainers]) { progress.hidden = false; }

const optionsFull = await this._settingsController.getOptionsFull();
const importDetails = {
Expand Down Expand Up @@ -411,7 +534,7 @@ export class DictionaryImportController {
} finally {
this._showErrors(errors);
prevention.end();
for (const progress of progressContainers) { progress.hidden = true; }
for (const progress of [...progressContainers, ...recommendedProgressContainers]) { progress.hidden = true; }
if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, false); }
this._setModifying(false);
this._triggerStorageChanged();
Expand Down
8 changes: 8 additions & 0 deletions ext/js/pages/welcome-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {Application} from '../application.js';
import {DocumentFocusController} from '../dom/document-focus-controller.js';
import {querySelectorNotNull} from '../dom/query-selector.js';
import {ExtensionContentController} from './common/extension-content-controller.js';
import {DictionaryController} from './settings/dictionary-controller.js';
import {DictionaryImportController} from './settings/dictionary-import-controller.js';
import {GenericSettingController} from './settings/generic-setting-controller.js';
import {LanguagesController} from './settings/languages-controller.js';
import {ModalController} from './settings/modal-controller.js';
Expand Down Expand Up @@ -88,6 +90,12 @@ await Application.main(true, async (application) => {
const genericSettingController = new GenericSettingController(settingsController);
preparePromises.push(setupGenericSettingsController(genericSettingController));

const dictionaryController = new DictionaryController(settingsController, modalController, statusFooter);
preparePromises.push(dictionaryController.prepare());

const dictionaryImportController = new DictionaryImportController(settingsController, modalController, statusFooter);
preparePromises.push(dictionaryImportController.prepare());

const simpleScanningInputController = new ScanInputsSimpleController(settingsController);
preparePromises.push(simpleScanningInputController.prepare());

Expand Down
12 changes: 12 additions & 0 deletions ext/templates-settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@
</select>
</div>
</div></template>
<template id="recommended-dictionaries-list-item-template">
<div class="settings-item">
<div class="settings-item-inner">
<div class="settings-item-left">
<strong><div class="settings-item-label"></div></strong>
</div>
<div class="settings-item-right">
<button type="button" class="action-button" data-action="import-recommended-dictionary" data-import-url="">Download</button>
</div>
</div>
</div>
</template>

<!-- Audio -->
<template id="audio-source-template"><div class="audio-source">
Expand Down
Loading

0 comments on commit 7dbced4

Please sign in to comment.