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 recommended dictionaries to welcome page #991

Merged
merged 33 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
be3fa43
Add dictionary import to welcome page
Kuuuube May 23, 2024
75b16d7
Initial prototype of recommended dict importing
Kuuuube May 23, 2024
10375c1
Disable button immediately
Kuuuube May 23, 2024
63b087b
Add queue to allow clicking multiple import buttons at once
Kuuuube May 23, 2024
b8906c6
Dynamic assignment of recommended dictionaries
Kuuuube May 23, 2024
e072029
Add to json.json
Kuuuube May 23, 2024
12e426d
Merge branch 'master' into welcome-page-dictionary
Kuuuube Jun 20, 2024
94d13bd
Change kanjidic to grab latest release
Kuuuube Jun 20, 2024
8f4927f
Make linter happy
Kuuuube Jun 20, 2024
d94ac5e
Add json schema
Kuuuube Jun 20, 2024
0b9e8e6
Add explicit types to dict array properties
Kuuuube Jun 20, 2024
5df34fe
Fix schema $id
Kuuuube Jun 20, 2024
3eb3d95
Change types file to dts and add labeled test type
Kuuuube Jun 20, 2024
6552b7d
Use static jitendex url
Kuuuube Jul 4, 2024
64dc081
Fix url path
Kuuuube Jul 4, 2024
c2a4031
Remove BCCWJ-LUW version from name
Kuuuube Jul 4, 2024
5b0f24c
Merge branch 'master' into welcome-page-dictionary
Kuuuube Jul 5, 2024
773ac3e
Only render recommended dictionaries on button click
Kuuuube Jul 16, 2024
6feb4b5
Clean up dictionary rendering
Kuuuube Jul 16, 2024
2bdc15f
Hide dictionaries when going from a language supporting recommended d…
Kuuuube Jul 16, 2024
e69f963
Merge branch 'master' into welcome-page-dictionary
Kuuuube Jul 29, 2024
2d8c55d
Update welcome page dict modals
Kuuuube Jul 29, 2024
f119f45
Fix spacing
Kuuuube Jul 29, 2024
54797ae
Add back recommended dictionary modals
Kuuuube Jul 29, 2024
eab65ad
Fix dictionary importing
Kuuuube Jul 29, 2024
a03db48
Remove unneeded progress selectors
Kuuuube Jul 29, 2024
0faa65f
Fix recommended dictionary modal rendering
Kuuuube Jul 29, 2024
812567a
Add pronunciation to recommended dictionary types
Kuuuube Jul 29, 2024
3fdb94c
Fix KANJIDIC name
Kuuuube Jul 29, 2024
706859f
Fix hiding and unhiding of elements
Kuuuube Jul 29, 2024
3816456
Move language select above recommended dictionaries
Kuuuube Jul 29, 2024
2e50478
Use BCCWJ combined instead of BCCWJ-LUW
Kuuuube Jul 29, 2024
5503f0c
show download progress
StefanVukovic99 Jul 29, 2024
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
14 changes: 14 additions & 0 deletions ext/data/recommended-dictionaries.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"ja": {
"terms": [
{"name": "Jitendex", "url": "https://github.com/stephenmk/stephenmk.github.io/releases/latest/download/jitendex-yomitan.zip"}
],
"kanji": [
{"name": "Kanjidic English", "url": "https://github.com/themoeway/jmdict-yomitan/releases/latest/download/KANJIDIC_english.zip"}
Kuuuube marked this conversation as resolved.
Show resolved Hide resolved
],
"frequency": [
{"name": "BCCWJ-LUW", "url": "https://github.com/toasted-nutbread/yomichan-bccwj-frequency-dictionary/releases/download/1.0.1/BCCWJ-LUW.zip"}
Kuuuube marked this conversation as resolved.
Show resolved Hide resolved
],
"grammar": []
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that updating is in, would it take a lot of work to list index.json files instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would have to ask @StefanVukovic99 on that one. But I think it's probably better to let another pr take that one. And static links will still need to be supported even if we add index.json support.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd like that so we can show more info, like the description, but not necessary for now

100 changes: 100 additions & 0 deletions ext/data/schemas/recommended-dictionaries-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"$id": "recommendedDictionaries",
Kuuuube marked this conversation as resolved.
Show resolved Hide resolved
"$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
}
}
}
}
},
"required": [
"terms",
"kanji",
"frequency",
"grammar"
],
"additionalProperties": false
}
}
}
118 changes: 116 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 @@ -87,10 +92,118 @@ export class DictionaryImportController {
this._importFileDrop.addEventListener('dragover', this._onFileDropOver.bind(this), false);
this._importFileDrop.addEventListener('dragleave', this._onFileDropLeave.bind(this), false);
this._importFileDrop.addEventListener('drop', this._onFileDrop.bind(this), false);

// 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 file = await fetch(url.trim())
.then((res) => res.blob())
.then((blob) => {
return new File([blob], 'fileFromURL');
});
await this._importDictionaries([file]);
} 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(document, '#recommended-term-dictionaries')},
{property: 'kanji', element: querySelectorNotNull(document, '#recommended-kanji-dictionaries')},
{property: 'frequency', element: querySelectorNotNull(document, '#recommended-frequency-dictionaries')},
{property: 'grammar', element: querySelectorNotNull(document, '#recommended-grammar-dictionaries')},
];

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) {
element.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 | null} dictionariesList
*/
_renderRecommendedDictionaryGroup(recommendedDictionaries, dictionariesList) {
for (const dictionary of recommendedDictionaries) {
if (dictionariesList) {
dictionariesList.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);
}
}
}

/** */
_onImportFileButtonClick() {
/** @type {HTMLInputElement} */ (this._importFileInput).click();
Expand Down Expand Up @@ -303,6 +416,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 progressBars = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`${progressSelector} .progress-bar`));
const infoLabels = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`${progressSelector} .progress-info`));
const statusLabels = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`${progressSelector} .progress-status`));
Expand All @@ -315,7 +429,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 @@ -365,7 +479,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 @@ -107,6 +107,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