diff options
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/display/display-audio.js | 99 | ||||
| -rw-r--r-- | ext/js/dom/popup-menu.js | 38 | 
2 files changed, 105 insertions, 32 deletions
| diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index f1feff5c..bb88c89e 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -31,6 +31,7 @@ class DisplayAudio {          this._cache = new Map();          this._menuContainer = document.querySelector('#popup-menus');          this._entriesToken = {}; +        this._openMenus = new Set();      }      get autoPlayAudioDelay() { @@ -198,9 +199,12 @@ class DisplayAudio {      }      _onAudioPlayMenuCloseClick(definitionIndex, expressionIndex, e) { -        const {detail: {action, item, menu}} = e; +        const {detail: {action, item, menu, shiftKey}} = e;          switch (action) {              case 'playAudioFromSource': +                if (shiftKey) { +                    e.preventDefault(); +                }                  this._playAudioFromSource(definitionIndex, expressionIndex, item);                  break;              case 'setPrimaryAudio': @@ -306,12 +310,14 @@ class DisplayAudio {          for (let i = 0, ii = sources.length; i < ii; ++i) {              const source = sources[i]; +            let cacheUpdated = false;              let infoListPromise;              let sourceInfo = sourceMap.get(source);              if (typeof sourceInfo === 'undefined') {                  infoListPromise = this._getExpressionAudioInfoList(source, expression, reading, details);                  sourceInfo = {infoListPromise, infoList: null};                  sourceMap.set(source, sourceInfo); +                cacheUpdated = true;              }              let {infoList} = sourceInfo; @@ -332,14 +338,17 @@ class DisplayAudio {                  }              } -            const audio = await this._createAudioFromInfoList(source, infoList, start, end); -            if (audio !== null) { return audio; } +            const {result, cacheUpdated: cacheUpdated2} = await this._createAudioFromInfoList(source, infoList, start, end); +            if (cacheUpdated || cacheUpdated2) { this._updateOpenMenu(); } +            if (result !== null) { return result; }          }          return null;      }      async _createAudioFromInfoList(source, infoList, start, end) { +        let result = null; +        let cacheUpdated = false;          for (let i = start; i < end; ++i) {              const item = infoList[i]; @@ -352,6 +361,8 @@ class DisplayAudio {                      item.audioPromise = audioPromise;                  } +                cacheUpdated = true; +                  try {                      audio = await audioPromise;                  } catch (e) { @@ -363,11 +374,12 @@ class DisplayAudio {                  item.audio = audio;              } -            if (audio === null) { continue; } - -            return {audio, source, infoListIndex: i}; +            if (audio !== null) { +                result = {audio, source, infoListIndex: i}; +                break; +            }          } -        return null; +        return {result, cacheUpdated};      }      async _createAudioFromInfo(info, source) { @@ -471,7 +483,13 @@ class DisplayAudio {          const {expression, reading} = expressionReading;          const popupMenu = this._createMenu(button, expression, reading); +        this._openMenus.add(popupMenu);          popupMenu.prepare(); +        popupMenu.on('close', this._onPopupMenuClose.bind(this)); +    } + +    _onPopupMenuClose({menu}) { +        this._openMenus.delete(menu);      }      _sourceIsDownloadable(source) { @@ -533,21 +551,36 @@ class DisplayAudio {      }      _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 menuBodyNode = menuNode.querySelector('.popup-menu-body'); +        const menuContainerNode = this._display.displayGenerator.instantiateTemplate('audio-button-popup-menu'); +        const menuBodyNode = menuContainerNode.querySelector('.popup-menu-body'); +        menuContainerNode.dataset.expression = expression; +        menuContainerNode.dataset.reading = reading;          // Set up items based on options and cache data +        this._createMenuItems(menuContainerNode, menuBodyNode, expression, reading); + +        // Update primary card audio display +        this._updateMenuPrimaryCardAudio(menuBodyNode, expression, reading); + +        // Create popup menu +        this._menuContainer.appendChild(menuContainerNode); +        return new PopupMenu(sourceButton, menuContainerNode); +    } + +    _createMenuItems(menuContainerNode, menuItemContainer, expression, reading) { +        const sources = this._getAudioSources(this._getAudioOptions()); +        const {displayGenerator} = this._display;          let showIcons = false; +        const currentItems = [...menuItemContainer.children];          for (const {source, displayName, isInOptions, downloadable} 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'); +                let node = this._getOrCreateMenuItem(currentItems, source, index); +                if (node === null) { +                    node = displayGenerator.instantiateTemplate('audio-button-popup-menu-item'); +                }                  const labelNode = node.querySelector('.popup-menu-item-audio-button .popup-menu-item-label');                  let label = displayName; @@ -571,17 +604,32 @@ class DisplayAudio {                  node.dataset.sourceInOptions = `${isInOptions}`;                  node.dataset.downloadable = `${downloadable}`; -                menuBodyNode.appendChild(node); +                menuItemContainer.appendChild(node);              }          } -        menuNode.dataset.showIcons = `${showIcons}`; +        for (const node of currentItems) { +            const {parentNode} = node; +            if (parentNode === null) { continue; } +            parentNode.removeChild(node); +        } +        menuContainerNode.dataset.showIcons = `${showIcons}`; +    } -        // Update primary card audio display -        this._updateMenuPrimaryCardAudio(menuBodyNode, expression, reading); +    _getOrCreateMenuItem(currentItems, source, index) { +        if (index === null) { index = 0; } +        index = `${index}`; +        for (let i = 0, ii = currentItems.length; i < ii; ++i) { +            const node = currentItems[i]; +            if (source !== node.dataset.source) { continue; } -        // Create popup menu -        this._menuContainer.appendChild(menuNode); -        return new PopupMenu(sourceButton, menuNode); +            let index2 = node.dataset.index; +            if (typeof index2 === 'undefined') { index2 = '0'; } +            if (index !== index2) { continue; } + +            currentItems.splice(i, 1); +            return node; +        } +        return null;      }      _getMenuItemEntries(source, expression, reading) { @@ -631,4 +679,13 @@ class DisplayAudio {              node.dataset.isPrimaryCardAudio = `${isPrimaryCardAudio}`;          }      } + +    _updateOpenMenu() { +        for (const menu of this._openMenus) { +            const menuContainerNode = menu.containerNode; +            const {expression, reading} = menuContainerNode.dataset; +            this._createMenuItems(menuContainerNode, menu.bodyNode, expression, reading); +            menu.updatePosition(); +        } +    }  } diff --git a/ext/js/dom/popup-menu.js b/ext/js/dom/popup-menu.js index 9ad4e260..af076baa 100644 --- a/ext/js/dom/popup-menu.js +++ b/ext/js/dom/popup-menu.js @@ -24,6 +24,7 @@ class PopupMenu extends EventDispatcher {          this._bodyNode = containerNode.querySelector('.popup-menu-body');          this._isClosed = false;          this._eventListeners = new EventListenerCollection(); +        this._itemEventListeners = new EventListenerCollection();      }      get sourceElement() { @@ -47,17 +48,13 @@ class PopupMenu extends EventDispatcher {      }      prepare() { -        const items = this._bodyNode.querySelectorAll('.popup-menu-item');          this._setPosition();          this._containerNode.focus();          this._eventListeners.addEventListener(window, 'resize', this._onWindowResize.bind(this), false);          this._eventListeners.addEventListener(this._containerNode, 'click', this._onMenuContainerClick.bind(this), false); -        const onMenuItemClick = this._onMenuItemClick.bind(this); -        for (const item of items) { -            this._eventListeners.addEventListener(item, 'click', onMenuItemClick, false); -        } +        this.updateMenuItems();          PopupMenu.openMenus.add(this); @@ -69,7 +66,20 @@ class PopupMenu extends EventDispatcher {      }      close(cancelable=true) { -        return this._close(null, 'close', cancelable); +        return this._close(null, 'close', cancelable, {}); +    } + +    updateMenuItems() { +        this._itemEventListeners.removeAllEventListeners(); +        const items = this._bodyNode.querySelectorAll('.popup-menu-item'); +        const onMenuItemClick = this._onMenuItemClick.bind(this); +        for (const item of items) { +            this._itemEventListeners.addEventListener(item, 'click', onMenuItemClick, false); +        } +    } + +    updatePosition() { +        this._setPosition();      }      // Private @@ -78,7 +88,7 @@ class PopupMenu extends EventDispatcher {          if (e.currentTarget !== e.target) { return; }          e.stopPropagation();          e.preventDefault(); -        this._close(null, 'outside', true); +        this._close(null, 'outside', true, e);      }      _onMenuItemClick(e) { @@ -86,11 +96,11 @@ class PopupMenu extends EventDispatcher {          if (item.disabled) { return; }          e.stopPropagation();          e.preventDefault(); -        this._close(item, 'item', true); +        this._close(item, 'item', true, e);      }      _onWindowResize() { -        this._close(null, 'resize', true); +        this._close(null, 'resize', true, {});      }      _setPosition() { @@ -172,15 +182,20 @@ class PopupMenu extends EventDispatcher {          menu.style.top = `${y}px`;      } -    _close(item, cause, cancelable) { +    _close(item, cause, cancelable, originalEvent) {          if (this._isClosed) { return true; }          const action = (item !== null ? item.dataset.menuAction : null); +        const {altKey=false, ctrlKey=false, metaKey=false, shiftKey=false} = originalEvent;          const detail = {              menu: this,              item,              action, -            cause +            cause, +            altKey, +            ctrlKey, +            metaKey, +            shiftKey          };          const result = this._sourceElement.dispatchEvent(new CustomEvent('menuClose', {bubbles: false, cancelable, detail}));          if (cancelable && !result) { return false; } @@ -189,6 +204,7 @@ class PopupMenu extends EventDispatcher {          this._isClosed = true;          this._eventListeners.removeAllEventListeners(); +        this._itemEventListeners.removeAllEventListeners();          if (this._containerNode.parentNode !== null) {              this._containerNode.parentNode.removeChild(this._containerNode);          } |