summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/css/display.css6
-rw-r--r--ext/css/material.css5
-rw-r--r--ext/display-templates.html10
-rw-r--r--ext/js/display/display-audio.js198
-rw-r--r--ext/js/display/display-generator.js4
5 files changed, 103 insertions, 120 deletions
diff --git a/ext/css/display.css b/ext/css/display.css
index bd381c6d..607368fc 100644
--- a/ext/css/display.css
+++ b/ext/css/display.css
@@ -1673,13 +1673,13 @@ button.footer-notification-close-button:active {
.audio-button-popup-menu[data-show-icons=false] .popup-menu-item-icon {
display: none;
}
-.popup-menu-item-icon[data-icon=checkmark] {
+.audio-button-popup-menu .popup-menu-item-icon[data-icon=checkmark] {
background-color: var(--success-color);
}
-.popup-menu-item-icon[data-icon=cross] {
+.audio-button-popup-menu .popup-menu-item-icon[data-icon=cross] {
background-color: var(--danger-color);
}
-.popup-menu-item[data-source-in-options=false][data-valid=null] {
+.audio-button-popup-menu .popup-menu-item-group[data-source-in-options=false][data-valid=null] .popup-menu-item {
color: var(--text-color-light1);
}
diff --git a/ext/css/material.css b/ext/css/material.css
index 1177baa1..2c7195cb 100644
--- a/ext/css/material.css
+++ b/ext/css/material.css
@@ -994,3 +994,8 @@ button.popup-menu-item:disabled {
padding: 0.5em 0.75em;
font-size: var(--font-size-small);
}
+.popup-menu-item-group {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: stretch;
+}
diff --git a/ext/display-templates.html b/ext/display-templates.html
index a470defa..82a9b97f 100644
--- a/ext/display-templates.html
+++ b/ext/display-templates.html
@@ -157,13 +157,7 @@
</label></template>
<!-- Popup menu -->
-<template id="audio-button-popup-menu-template"><div class="popup-menu-container scan-disable audio-button-popup-menu" tabindex="-1" role="dialog"><div class="popup-menu popup-menu-auto-size"><div class="popup-menu-body">
- <button class="popup-menu-item" data-menu-action="playAudioFromSource" data-source="jpod101"><div class="popup-menu-item-icon icon" data-icon="none"></div><span class="popup-menu-item-label">JapanesePod101</span></button>
- <button class="popup-menu-item" data-menu-action="playAudioFromSource" data-source="jpod101-alternate"><div class="popup-menu-item-icon icon" data-icon="none"></div><span class="popup-menu-item-label">JapanesePod101 (Alternate)</span></button>
- <button class="popup-menu-item" data-menu-action="playAudioFromSource" data-source="jisho"><div class="popup-menu-item-icon icon" data-icon="none"></div><span class="popup-menu-item-label">Jisho.org</span></button>
- <button class="popup-menu-item" data-menu-action="playAudioFromSource" data-source="text-to-speech"><div class="popup-menu-item-icon icon" data-icon="none"></div><span class="popup-menu-item-label">Text-to-speech</span></button>
- <button class="popup-menu-item" data-menu-action="playAudioFromSource" data-source="text-to-speech-reading"><div class="popup-menu-item-icon icon" data-icon="none"></div><span class="popup-menu-item-label">Text-to-speech (Kana reading)</span></button>
- <button class="popup-menu-item" data-menu-action="playAudioFromSource" data-source="custom"><div class="popup-menu-item-icon icon" data-icon="none"></div><span class="popup-menu-item-label">Custom</span></button>
-</div></div></div></template>
+<template id="audio-button-popup-menu-template"><div class="popup-menu-container scan-disable audio-button-popup-menu" tabindex="-1" role="dialog"><div class="popup-menu popup-menu-auto-size"><div class="popup-menu-body"></div></div></div></template>
+<template id="audio-button-popup-menu-item-template"><div class="popup-menu-item-group"><button class="popup-menu-item" data-menu-action="playAudioFromSource"><div class="popup-menu-item-icon icon" data-icon="none"></div><span class="popup-menu-item-label"></span></button></div></template>
</body></html>
diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js
index 24f2dd7b..0ccb4eef 100644
--- a/ext/js/display/display-audio.js
+++ b/ext/js/display/display-audio.js
@@ -189,7 +189,10 @@ class DisplayAudio {
switch (action) {
case 'playAudioFromSource':
{
- const {source, index} = item.dataset;
+ const group = item.closest('.popup-menu-item-group');
+ if (group === null) { break; }
+
+ const {source, index} = group.dataset;
let sourceDetailsMap = null;
if (typeof index !== 'undefined') {
const index2 = Number.parseInt(index, 10);
@@ -405,139 +408,120 @@ class DisplayAudio {
popupMenu.prepare();
}
- _createMenu(button, expression, reading) {
- // Options
- const {sources, textToSpeechVoice, customSourceUrl} = this._getAudioOptions();
+ _getAudioSources(audioOptions) {
+ const {sources, textToSpeechVoice, customSourceUrl} = audioOptions;
+ const ttsSupported = (textToSpeechVoice.length > 0);
+ const customSupported = (customSourceUrl.length > 0);
+
const sourceIndexMap = new Map();
- for (let i = 0, ii = sources.length; i < ii; ++i) {
+ const optionsSourcesCount = sources.length;
+ for (let i = 0; i < optionsSourcesCount; ++i) {
sourceIndexMap.set(sources[i], i);
}
- // Create menu
- const menuNode = this._display.displayGenerator.createPopupMenu('audio-button');
-
- // Create menu item metadata
- const menuItems = [];
- const menuItemNodes = menuNode.querySelectorAll('.popup-menu-item');
- for (let i = 0, ii = menuItemNodes.length; i < ii; ++i) {
- const node = menuItemNodes[i];
- const {source} = node.dataset;
+ const rawSources = [
+ ['jpod101', 'JapanesePod101', true],
+ ['jpod101-alternate', 'JapanesePod101 (Alternate)', true],
+ ['jisho', 'Jisho.org', true],
+ ['text-to-speech', 'Text-to-speech', ttsSupported],
+ ['text-to-speech-reading', 'Text-to-speech (Kana reading)', ttsSupported],
+ ['custom', 'Custom', customSupported]
+ ];
+
+ const results = [];
+ for (const [source, displayName, supported] of rawSources) {
+ if (!supported) { continue; }
let optionsIndex = sourceIndexMap.get(source);
- if (typeof optionsIndex === 'undefined') { optionsIndex = null; }
- menuItems.push({node, source, index: i, optionsIndex});
+ const isInOptions = typeof optionsIndex !== 'undefined';
+ if (!isInOptions) {
+ optionsIndex = optionsSourcesCount;
+ }
+ results.push({
+ source,
+ displayName,
+ index: results.length,
+ optionsIndex,
+ isInOptions
+ });
}
// Sort according to source order in options
- menuItems.sort((a, b) => {
- const ai = a.optionsIndex;
- const bi = b.optionsIndex;
- if (ai !== null) {
- if (bi !== null) {
- const i = ai - bi;
- if (i !== 0) { return i; }
- } else {
- return -1;
- }
- } else {
- if (bi !== null) {
- return 1;
- }
- }
- return a.index - b.index;
+ results.sort((a, b) => {
+ const i = a.optionsIndex - b.optionsIndex;
+ return i !== 0 ? i : a.index - b.index;
});
- // Set up items based on cache data
- const sourceMap = this._cache.get(this._getExpressionReadingKey(expression, reading));
- const menuEntryMap = new Map();
+ return results;
+ }
+
+ _createMenu(sourceButton, expression, reading) {
+ // Options
+ const sources = this._getAudioSources(this._getAudioOptions());
+
+ // Create menu
+ const {displayGenerator} = this._display;
+ const menuNode = displayGenerator.instantiateTemplate('audio-button-popup-menu');
+ const menuBody = menuNode.querySelector('.popup-menu-body');
+
+ // Set up items based on options and cache data
let showIcons = false;
- for (let i = 0, ii = menuItems.length; i < ii; ++i) {
- const {node, source, optionsIndex} = menuItems[i];
- const entries = this._getMenuItemEntries(node, sourceMap, source);
- menuEntryMap.set(source, entries);
- for (const {node: node2, valid, index} of entries) {
+ for (const {source, displayName, isInOptions} of sources) {
+ const entries = this._getMenuItemEntries(source, expression, reading);
+ for (let i = 0, ii = entries.length; i < ii; ++i) {
+ const {valid, index, name} = entries[i];
+ const node = displayGenerator.instantiateTemplate('audio-button-popup-menu-item');
+
+ const labelNode = node.querySelector('.popup-menu-item-label');
+ let label = displayName;
+ if (ii > 1) { label = `${label} ${i + 1}`; }
+ if (typeof name === 'string' && name.length > 0) { label += `: ${name}`; }
+ labelNode.textContent = label;
+
if (valid !== null) {
- const icon = node2.querySelector('.popup-menu-item-icon');
+ const icon = node.querySelector('.popup-menu-item-icon');
icon.dataset.icon = valid ? 'checkmark' : 'cross';
showIcons = true;
}
+ node.dataset.source = source;
if (index !== null) {
- node2.dataset.index = `${index}`;
+ node.dataset.index = `${index}`;
}
- node2.dataset.valid = `${valid}`;
- node2.dataset.sourceInOptions = `${optionsIndex !== null}`;
- node2.style.order = `${i}`;
+ node.dataset.valid = `${valid}`;
+ node.dataset.sourceInOptions = `${isInOptions}`;
+
+ menuBody.appendChild(node);
}
}
menuNode.dataset.showIcons = `${showIcons}`;
- // Hide options
- if (textToSpeechVoice.length === 0) {
- this._setMenuItemEntriesHidden(menuEntryMap, 'text-to-speech', true);
- this._setMenuItemEntriesHidden(menuEntryMap, 'text-to-speech-reading', true);
- }
- if (customSourceUrl.length === 0) {
- this._setMenuItemEntriesHidden(menuEntryMap, 'custom', true);
- }
-
// Create popup menu
this._menuContainer.appendChild(menuNode);
- return new PopupMenu(button, menuNode);
+ return new PopupMenu(sourceButton, menuNode);
}
- _getMenuItemEntries(node, sourceMap, source) {
- const entries = [{node, valid: null, index: null}];
-
- const nextNode = node.nextSibling;
-
- if (typeof sourceMap === 'undefined') { return entries; }
-
- const sourceInfo = sourceMap.get(source);
- if (typeof sourceInfo === 'undefined') { return entries; }
-
- const {infoList} = sourceInfo;
- if (infoList === null) { return entries; }
-
- if (infoList.length === 0) {
- entries[0].valid = false;
- return entries;
- }
-
- const defaultLabel = node.querySelector('.popup-menu-item-label').textContent;
+ _getMenuItemEntries(source, expression, reading) {
+ const sourceMap = this._cache.get(this._getExpressionReadingKey(expression, reading));
+ if (typeof sourceMap !== 'undefined') {
+ const sourceInfo = sourceMap.get(source);
+ if (typeof sourceInfo !== 'undefined') {
+ const {infoList} = sourceInfo;
+ if (infoList !== null) {
+ const ii = infoList.length;
+ if (ii === 0) {
+ return [{valid: false, index: null, name: null}];
+ }
- for (let i = 0, ii = infoList.length; i < ii; ++i) {
- // Get/create entry
- let entry;
- if (i < entries.length) {
- entry = entries[i];
- } else {
- const node2 = node.cloneNode(true);
- nextNode.parentNode.insertBefore(node2, nextNode);
- entry = {node: node2, valid: null, index: null};
- entries.push(entry);
+ const results = [];
+ for (let i = 0; i < ii; ++i) {
+ const {audio, audioResolved, info: {name}} = infoList[i];
+ const valid = audioResolved ? (audio !== null) : null;
+ const entry = {valid, index: i, name};
+ results.push(entry);
+ }
+ return results;
+ }
}
-
- // Entry info
- entry.index = i;
-
- const {audio, audioResolved, info: {name}} = infoList[i];
- if (audioResolved) { entry.valid = (audio !== null); }
-
- const labelNode = entry.node.querySelector('.popup-menu-item-label');
- let label = defaultLabel;
- if (ii > 1) { label = `${label} ${i + 1}`; }
- if (typeof name === 'string' && name.length > 0) { label += `: ${name}`; }
- labelNode.textContent = label;
- }
-
- return entries;
- }
-
- _setMenuItemEntriesHidden(menuEntryMap, source, hidden) {
- const entries = menuEntryMap.get(source);
- if (typeof entries === 'undefined') { return; }
-
- for (const {node} of entries) {
- node.hidden = hidden;
}
+ return [{valid: null, index: null, name: null}];
}
}
diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js
index 18159f68..1b3908fe 100644
--- a/ext/js/display/display-generator.js
+++ b/ext/js/display/display-generator.js
@@ -213,8 +213,8 @@ class DisplayGenerator {
return this._templates.instantiate('profile-list-item');
}
- createPopupMenu(name) {
- return this._templates.instantiate(`${name}-popup-menu`);
+ instantiateTemplate(name) {
+ return this._templates.instantiate(name);
}
// Private