diff options
| -rw-r--r-- | ext/fg/float.html | 7 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 12 | ||||
| -rw-r--r-- | ext/fg/js/popup-factory.js | 14 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy.js | 8 | ||||
| -rw-r--r-- | ext/fg/js/popup-window.js | 8 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 19 | ||||
| -rw-r--r-- | ext/mixed/css/display.css | 50 | ||||
| -rw-r--r-- | ext/mixed/css/popup-outer.css | 3 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 74 | 
9 files changed, 189 insertions, 6 deletions
| diff --git a/ext/fg/float.html b/ext/fg/float.html index 5874f44d..50c3b691 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -67,6 +67,13 @@      </div>  </div> +<div class="frame-resizer-container"> +    <div class="frame-resizer-sizer1"><div class="frame-resizer-sizer2 frame-resizer-sizer2-with-min-size scrollbar"></div></div> +    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1" preserveAspectRatio="none" class="frame-resizer-svg"> +        <path d="M1 0L1 1L0 1Z" class="frame-resizer-handle" id="frame-resizer-handle"/> +    </svg> +</div> +  <!-- Scripts -->  <script src="/mixed/js/core.js"></script>  <script src="/mixed/js/yomichan.js"></script> diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 49c8a91c..cb105341 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -119,7 +119,9 @@ class Frontend {              ['copySelection',           {async: false, handler: this._onApiCopySelection.bind(this)}],              ['getSelectionText',        {async: false, handler: this._onApiGetSelectionText.bind(this)}],              ['getPopupInfo',            {async: false, handler: this._onApiGetPopupInfo.bind(this)}], -            ['getDocumentInformation',  {async: false, handler: this._onApiGetDocumentInformation.bind(this)}] +            ['getDocumentInformation',  {async: false, handler: this._onApiGetDocumentInformation.bind(this)}], +            ['getFrameSize',            {async: true,  handler: this._onApiGetFrameSize.bind(this)}], +            ['setFrameSize',            {async: true,  handler: this._onApiSetFrameSize.bind(this)}]          ]);          this._updateContentScale(); @@ -203,6 +205,14 @@ class Frontend {          return await this._popupFactory.clearAllVisibleOverride(token);      } +    async _onApiGetFrameSize() { +        return await this._popup.getFrameSize(); +    } + +    async _onApiSetFrameSize({width, height}) { +        return await this._popup.setFrameSize(width, height); +    } +      // Private      _onResize() { diff --git a/ext/fg/js/popup-factory.js b/ext/fg/js/popup-factory.js index 252bcdf4..9a6c2ccd 100644 --- a/ext/fg/js/popup-factory.js +++ b/ext/fg/js/popup-factory.js @@ -48,7 +48,9 @@ class PopupFactory {              ['clearAutoPlayTimer',   {async: false, handler: this._onApiClearAutoPlayTimer.bind(this)}],              ['setContentScale',      {async: false, handler: this._onApiSetContentScale.bind(this)}],              ['updateTheme',          {async: false, handler: this._onApiUpdateTheme.bind(this)}], -            ['setCustomOuterCss',    {async: false, handler: this._onApiSetCustomOuterCss.bind(this)}] +            ['setCustomOuterCss',    {async: false, handler: this._onApiSetCustomOuterCss.bind(this)}], +            ['popup.getFrameSize',   {async: true,  handler: this._onApiGetFrameSize.bind(this)}], +            ['popup.setFrameSize',   {async: true,  handler: this._onApiSetFrameSize.bind(this)}]          ]);      } @@ -265,6 +267,16 @@ class PopupFactory {          return popup.setCustomOuterCss(css, useWebExtensionApi);      } +    async _onApiGetFrameSize({id}) { +        const popup = this._getPopup(id); +        return await popup.getFrameSize(); +    } + +    async _onApiSetFrameSize({id, width, height}) { +        const popup = this._getPopup(id); +        return await popup.setFrameSize(width, height); +    } +      // Private functions      _getPopup(id) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 26dce0d1..a03b58e8 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -149,6 +149,14 @@ class PopupProxy extends EventDispatcher {          return new DOMRect(0, 0, 0, 0);      } +    getFrameSize() { +        return this._invokeSafe('popup.getFrameSize', {id: this._id}, {width: 0, height: 0, valid: false}); +    } + +    setFrameSize(width, height) { +        return this._invokeSafe('popup.setFrameSize', {id: this._id, width, height}); +    } +      // Private      _invoke(action, params={}) { diff --git a/ext/fg/js/popup-window.js b/ext/fg/js/popup-window.js index 3d707a9e..3d68beb8 100644 --- a/ext/fg/js/popup-window.js +++ b/ext/fg/js/popup-window.js @@ -132,6 +132,14 @@ class PopupWindow extends EventDispatcher {          return new DOMRect(0, 0, 0, 0);      } +    async getFrameSize() { +        return {width: 0, height: 0, valid: false}; +    } + +    async setFrameSize(_width, _height) { +        return false; +    } +      // Private      async _invoke(open, action, params={}, defaultReturnValue) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index ffd72dca..c24ffb5c 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -208,6 +208,16 @@ class Popup extends EventDispatcher {          return this._frame.getBoundingClientRect();      } +    async getFrameSize() { +        const rect = this._frame.getBoundingClientRect(); +        return {width: rect.width, height: rect.height, valid: true}; +    } + +    async setFrameSize(width, height) { +        this._setFrameSize(width, height); +        return true; +    } +      // Private functions      _onFrameMouseOver() { @@ -397,8 +407,7 @@ class Popup extends EventDispatcher {          frame.style.left = `${x}px`;          frame.style.top = `${y}px`; -        frame.style.width = `${width}px`; -        frame.style.height = `${height}px`; +        this._setFrameSize(width, height);          this._setVisible(true);          if (this._child !== null) { @@ -406,6 +415,12 @@ class Popup extends EventDispatcher {          }      } +    _setFrameSize(width, height) { +        const {style} = this._frame; +        style.width = `${width}px`; +        style.height = `${height}px`; +    } +      _setVisible(visible) {          this._visible.defaultValue = visible;      } diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 605c0148..0a676a8f 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -1119,6 +1119,52 @@ button.action-button {  } +/* Frame resizer */ +.frame-resizer-container { +    position: fixed; +    bottom: 0; +    right: 0; +    z-index: 100; +    background: transparent; +    pointer-events: none; +    user-select: none; +} +.frame-resizer-sizer1 { +    padding-top: 100%; +    line-height: 0; +} +.frame-resizer-sizer2 { +    display: inline-block; +    overflow-x: hidden; +    overflow-y: scroll; +    vertical-align: bottom; +} +.frame-resizer-sizer2.frame-resizer-sizer2-with-min-size { +    min-width: 1em; +} +.frame-resizer-svg { +    display: block; +    position: absolute; +    bottom: 0; +    right: 0; +    width: 75%; +    height: 75%; +} +.frame-resizer-handle { +    fill: var(--default-text-color); +    opacity: 0.125; +    cursor: se-resize; +    pointer-events: auto; +    transition: +        fill var(--animation-duration) linear, +        opacity var(--animation-duration) linear; +} +.frame-resizer-handle:hover { +    fill: var(--accent-color); +    opacity: 1; +} + +  /* Conditional styles */  :root:not([data-enable-search-tags=true]) .tag[data-category=search] {      display: none; @@ -1221,3 +1267,7 @@ button.action-button {  :root:not([data-glossary-layout-mode=compact]) .term-glossary-image-description {      display: block;  } + +:root[data-popup-display-mode=full-width] .frame-resizer-container { +    display: none; +} diff --git a/ext/mixed/css/popup-outer.css b/ext/mixed/css/popup-outer.css index 74307d9f..0ee8df9e 100644 --- a/ext/mixed/css/popup-outer.css +++ b/ext/mixed/css/popup-outer.css @@ -22,7 +22,7 @@ iframe.yomichan-popup {      border: 1em solid #999999;      box-shadow: 0 0 10em rgba(0, 0, 0, 0.5);      position: fixed; -    resize: both; +    resize: none;      visibility: hidden;      z-index: 2147483647;      box-sizing: border-box; @@ -38,7 +38,6 @@ iframe.yomichan-popup[data-outer-theme=auto][data-site-color=dark] {  iframe.yomichan-popup[data-popup-display-mode=full-width] {      border-left: none;      border-right: none; -    resize: none;  }  iframe.yomichan-popup[data-popup-display-mode=full-width][data-below=true] {      border-bottom: none; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b9daa9a0..b19d07e2 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -109,6 +109,11 @@ class Display extends EventDispatcher {          this._browser = null;          this._copyTextarea = null;          this._definitionTextScanner = null; +        this._frameResizeToken = null; +        this._frameResizeHandle = document.querySelector('#frame-resizer-handle'); +        this._frameResizeStartSize = null; +        this._frameResizeStartOffset = null; +        this._frameResizeEventListeners = new EventListenerCollection();          this.registerActions([              ['close',             () => { this.onEscape(); }], @@ -228,6 +233,10 @@ class Display extends EventDispatcher {              this._navigationNextButton.addEventListener('click', this._onNextTermView.bind(this), false);          } +        if (this._frameResizeHandle !== null) { +            this._frameResizeHandle.addEventListener('mousedown', this._onFrameResizerMouseDown.bind(this), false); +        } +          // Final preparation          this._updateFocusedElement();      } @@ -801,6 +810,7 @@ class Display extends EventDispatcher {          data.showPitchAccentPositionNotation = `${options.general.showPitchAccentPositionNotation}`;          data.showPitchAccentGraph = `${options.general.showPitchAccentGraph}`;          data.debug = `${options.general.debugInfo}`; +        data.popupDisplayMode = `${options.general.popupDisplayMode}`;      }      _updateTheme(themeName) { @@ -1795,4 +1805,68 @@ class Display extends EventDispatcher {          this._definitionTextScanner.clearSelection(true);          this.setContent(details);      } + +    _onFrameResizerMouseDown(e) { +        if (e.button !== 0) { return; } +        // Don't do e.preventDefault() here; this allows mousemove events to be processed +        // if the pointer moves out of the frame. +        this._startFrameResize(e); +    } + +    _onFrameResizerMouseUp() { +        this._stopFrameResize(); +    } + +    _onFrameResizerWindowBlur() { +        this._stopFrameResize(); +    } + +    _onFrameResizerMouseMove(e) { +        if ((e.buttons & 0x1) === 0x0) { +            this._stopFrameResize(); +        } else { +            if (this._frameResizeStartSize === null) { return; } +            const {clientX: x, clientY: y} = e; +            this._updateFrameSize(x, y); +        } +    } + +    _startFrameResize(e) { +        if (this._frameResizeToken !== null) { return; } + +        const {clientX: x, clientY: y} = e; +        const token = {}; +        this._frameResizeToken = token; +        this._frameResizeStartOffset = {x, y}; +        this._frameResizeEventListeners.addEventListener(window, 'mouseup', this._onFrameResizerMouseUp.bind(this), false); +        this._frameResizeEventListeners.addEventListener(window, 'blur', this._onFrameResizerWindowBlur.bind(this), false); +        this._frameResizeEventListeners.addEventListener(window, 'mousemove', this._onFrameResizerMouseMove.bind(this), false); + +        this._initializeFrameResize(token); +    } + +    async _initializeFrameResize(token) { +        const size = await this._invokeOwner('getFrameSize'); +        if (this._frameResizeToken !== token) { return; } +        this._frameResizeStartSize = size; +    } + +    _stopFrameResize() { +        if (this._frameResizeToken === null) { return; } + +        this._frameResizeEventListeners.removeAllEventListeners(); +        this._frameResizeStartSize = null; +        this._frameResizeStartOffset = null; +        this._frameResizeToken = null; +    } + +    async _updateFrameSize(x, y) { +        const handleSize = this._frameResizeHandle.getBoundingClientRect(); +        let {width, height} = this._frameResizeStartSize; +        width += x - this._frameResizeStartOffset.x; +        height += y - this._frameResizeStartOffset.y; +        width = Math.max(Math.max(0, handleSize.width), width); +        height = Math.max(Math.max(0, handleSize.height), height); +        await this._invokeOwner('setFrameSize', {width, height}); +    }  } |