/*
* Copyright (C) 2023 Yomitan Authors
* Copyright (C) 2021-2022 Yomichan Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import {querySelectorNotNull} from '../../dom/query-selector.js';
import {yomitan} from '../../yomitan.js';
export class SortFrequencyDictionaryController {
/**
* @param {import('./settings-controller.js').SettingsController} settingsController
*/
constructor(settingsController) {
/** @type {import('./settings-controller.js').SettingsController} */
this._settingsController = settingsController;
/** @type {HTMLSelectElement} */
this._sortFrequencyDictionarySelect = querySelectorNotNull(document, '#sort-frequency-dictionary');
/** @type {HTMLSelectElement} */
this._sortFrequencyDictionaryOrderSelect = querySelectorNotNull(document, '#sort-frequency-dictionary-order');
/** @type {HTMLButtonElement} */
this._sortFrequencyDictionaryOrderAutoButton = querySelectorNotNull(document, '#sort-frequency-dictionary-order-auto');
/** @type {HTMLElement} */
this._sortFrequencyDictionaryOrderContainerNode = querySelectorNotNull(document, '#sort-frequency-dictionary-order-container');
/** @type {?import('core').TokenObject} */
this._getDictionaryInfoToken = null;
}
/** */
async prepare() {
await this._onDatabaseUpdated();
yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this));
this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));
this._sortFrequencyDictionarySelect.addEventListener('change', this._onSortFrequencyDictionarySelectChange.bind(this));
this._sortFrequencyDictionaryOrderSelect.addEventListener('change', this._onSortFrequencyDictionaryOrderSelectChange.bind(this));
this._sortFrequencyDictionaryOrderAutoButton.addEventListener('click', this._onSortFrequencyDictionaryOrderAutoButtonClick.bind(this));
}
// Private
/** */
async _onDatabaseUpdated() {
/** @type {?import('core').TokenObject} */
const token = {};
this._getDictionaryInfoToken = token;
const dictionaries = await this._settingsController.getDictionaryInfo();
if (this._getDictionaryInfoToken !== token) { return; }
this._getDictionaryInfoToken = null;
this._updateDictionaryOptions(dictionaries);
const options = await this._settingsController.getOptions();
const optionsContext = this._settingsController.getOptionsContext();
this._onOptionsChanged({options, optionsContext});
}
/**
* @param {import('settings-controller').EventArgument<'optionsChanged'>} details
*/
_onOptionsChanged({options}) {
const {sortFrequencyDictionary, sortFrequencyDictionaryOrder} = options.general;
/** @type {HTMLSelectElement} */ (this._sortFrequencyDictionarySelect).value = (sortFrequencyDictionary !== null ? sortFrequencyDictionary : '');
/** @type {HTMLSelectElement} */ (this._sortFrequencyDictionaryOrderSelect).value = sortFrequencyDictionaryOrder;
/** @type {HTMLElement} */ (this._sortFrequencyDictionaryOrderContainerNode).hidden = (sortFrequencyDictionary === null);
}
/** */
_onSortFrequencyDictionarySelectChange() {
const {value} = /** @type {HTMLSelectElement} */ (this._sortFrequencyDictionarySelect);
this._setSortFrequencyDictionaryValue(value !== '' ? value : null);
}
/** */
_onSortFrequencyDictionaryOrderSelectChange() {
const {value} = /** @type {HTMLSelectElement} */ (this._sortFrequencyDictionaryOrderSelect);
const value2 = this._normalizeSortFrequencyDictionaryOrder(value);
if (value2 === null) { return; }
this._setSortFrequencyDictionaryOrderValue(value2);
}
/** */
_onSortFrequencyDictionaryOrderAutoButtonClick() {
const {value} = /** @type {HTMLSelectElement} */ (this._sortFrequencyDictionarySelect);
if (value === '') { return; }
this._autoUpdateOrder(value);
}
/**
* @param {import('dictionary-importer').Summary[]} dictionaries
*/
_updateDictionaryOptions(dictionaries) {
const fragment = document.createDocumentFragment();
let option = document.createElement('option');
option.value = '';
option.textContent = 'None';
fragment.appendChild(option);
for (const {title, counts} of dictionaries) {
if (this._dictionaryHasNoFrequencies(counts)) { continue; }
option = document.createElement('option');
option.value = title;
option.textContent = title;
fragment.appendChild(option);
}
const select = /** @type {HTMLSelectElement} */ (this._sortFrequencyDictionarySelect);
select.textContent = '';
select.appendChild(fragment);
}
/**
* @param {?string} value
*/
async _setSortFrequencyDictionaryValue(value) {
/** @type {HTMLElement} */ (this._sortFrequencyDictionaryOrderContainerNode).hidden = (value === null);
await this._settingsController.setProfileSetting('general.sortFrequencyDictionary', value);
if (value !== null) {
await this._autoUpdateOrder(value);
}
}
/**
* @param {import('settings').SortFrequencyDictionaryOrder} value
*/
async _setSortFrequencyDictionaryOrderValue(value) {
await this._settingsController.setProfileSetting('general.sortFrequencyDictionaryOrder', value);
}
/**
* @param {string} dictionary
*/
async _autoUpdateOrder(dictionary) {
const order = await this._getFrequencyOrder(dictionary);
if (order === 0) { return; }
const value = (order > 0 ? 'descending' : 'ascending');
/** @type {HTMLSelectElement} */ (this._sortFrequencyDictionaryOrderSelect).value = value;
await this._setSortFrequencyDictionaryOrderValue(value);
}
/**
* @param {string} dictionary
* @returns {Promise}
*/
async _getFrequencyOrder(dictionary) {
const moreCommonTerms = ['来る', '言う', '出る', '入る', '方', '男', '女', '今', '何', '時'];
const lessCommonTerms = ['行なう', '論じる', '過す', '行方', '人口', '猫', '犬', '滝', '理', '暁'];
const terms = [...moreCommonTerms, ...lessCommonTerms];
const frequencies = await yomitan.api.getTermFrequencies(
terms.map((term) => ({term, reading: null})),
[dictionary]
);
/** @type {Map} */
const termDetails = new Map();
const moreCommonTermDetails = [];
const lessCommonTermDetails = [];
for (const term of moreCommonTerms) {
const details = {hasValue: false, minValue: Number.MAX_SAFE_INTEGER, maxValue: Number.MIN_SAFE_INTEGER};
termDetails.set(term, details);
moreCommonTermDetails.push(details);
}
for (const term of lessCommonTerms) {
const details = {hasValue: false, minValue: Number.MAX_SAFE_INTEGER, maxValue: Number.MIN_SAFE_INTEGER};
termDetails.set(term, details);
lessCommonTermDetails.push(details);
}
for (const {term, frequency} of frequencies) {
const details = termDetails.get(term);
if (typeof details === 'undefined') { continue; }
details.minValue = Math.min(details.minValue, frequency);
details.maxValue = Math.max(details.maxValue, frequency);
details.hasValue = true;
}
let result = 0;
for (const details1 of moreCommonTermDetails) {
if (!details1.hasValue) { continue; }
for (const details2 of lessCommonTermDetails) {
if (!details2.hasValue) { continue; }
result += Math.sign(details1.maxValue - details2.minValue) + Math.sign(details1.minValue - details2.maxValue);
}
}
return Math.sign(result);
}
/**
* @param {import('dictionary-importer').SummaryCounts} counts
* @returns {boolean}
*/
_dictionaryHasNoFrequencies(counts) {
if (typeof counts !== 'object' || counts === null) { return false; }
const {termMeta} = counts;
if (typeof termMeta !== 'object' || termMeta === null) { return false; }
return termMeta.freq <= 0;
}
/**
* @param {string} value
* @returns {?import('settings').SortFrequencyDictionaryOrder}
*/
_normalizeSortFrequencyDictionaryOrder(value) {
switch (value) {
case 'ascending':
case 'descending':
return value;
default:
return null;
}
}
}