diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/ask.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/ask.js | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/ask.js b/packages/meshbay-hub/src/meshbay_hub/static/ask.js new file mode 100644 index 0000000..8a8b895 --- /dev/null +++ b/packages/meshbay-hub/src/meshbay_hub/static/ask.js @@ -0,0 +1,70 @@ +import { html, render, useEffect, useRef } from './vendor/htm-preact.js'; +import { t } from './i18n.js'; + +/** + * `confirm` and `alert`, drawn by the page instead of the browser. + * + * In the desktop client a native `confirm()` leaves the window unable to type: + * once it closes, `document.hasFocus()` stays false, a click still moves + * `activeElement` to a field, and every keystroke after it goes nowhere — until + * the window loses and regains focus. Found as "the Create group fields are + * frozen" right after removing a member, and reproduced against Electron 44 by + * real X input events (`test_no_native_dialogs_in_the_spa.py`). `prompt` was + * already banned for throwing; the other two fail more quietly, and later. + * + * Both return a promise, so a call site reads as it did: `if (!(await + * ask(msg))) return;`. + */ + +function Dialog({ message, cancellable, onDone }) { + const okRef = useRef(null); + useEffect(() => { if (okRef.current) okRef.current.focus(); }, []); + + const onKeyDown = (e) => { + if (e.key === 'Escape') { e.preventDefault(); onDone(!cancellable); } + }; + + return html` + <div class="video-overlay" onClick=${(e) => { + if (e.target.classList.contains('video-overlay')) onDone(!cancellable); + }}> + <form class="music-detail playlist-modal" role="alertdialog" aria-modal="true" + onKeyDown=${onKeyDown} + onSubmit=${(e) => { e.preventDefault(); onDone(true); }}> + <div class="playlist-modal-body"> + <div class="ask-message">${message}</div> + <div class="playlist-modal-actions"> + ${cancellable && html` + <button type="button" class="tb-btn" onClick=${() => onDone(false)}> + ${t('dialog.cancel')}</button>`} + <button type="submit" class="admin-btn" ref=${okRef}>${t('dialog.ok')}</button> + </div> + </div> + </form> + </div> + `; +} + +function open(message, cancellable) { + return new Promise((resolve) => { + const host = document.createElement('div'); + document.body.appendChild(host); + const previous = document.activeElement; + let settled = false; + const onDone = (value) => { + if (settled) return; + settled = true; + render(null, host); + host.remove(); + if (previous && previous.isConnected && typeof previous.focus === 'function') { + previous.focus(); + } + resolve(value); + }; + render(html`<${Dialog} message=${String(message)} cancellable=${cancellable} + onDone=${onDone} />`, host); + }); +} + +export const ask = (message) => open(message, true); +export const tell = (message) => open(message, false); |