feat(quit): close-window button + manual hint on the farewell overlay
The farewell overlay already attempted ``window.top.close()`` after a Close click — but browsers only honour that for tabs that JS opened (Chrome --app windows qualify; a regular browser tab does not). For users whose Chrome wasn't auto-detected and who fall back to ``webbrowser.open``, the overlay stays put and they had no in-page way to close. Add to the overlay HTML: - A "Close this window" button (uses the user-gesture path, which has slightly looser browser rules than auto-close). - A hidden hint paragraph that reveals itself 250 ms after the button is clicked IF the window is still here, telling the user to press Ctrl+W (⌘W on Mac). Wired through the existing _farewell_script template + ``_js_html_safe`` escaping so neither label can break out of the JS string literal. New i18n keys (en + es): ``quit.close_window_button`` and ``quit.close_hint``. The existing auto-close attempt remains — Chrome --app users still get their window closed without touching the button. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,13 @@ _FAREWELL_SCRIPT_TEMPLATE = """
|
||||
//
|
||||
// We don't navigate to a data: URL here because Chrome blocks
|
||||
// top-frame navigation to data: URLs (anti-phishing, Chrome 60+).
|
||||
//
|
||||
// The overlay carries a manual "Close this window" button as a
|
||||
// fallback for browsers that block the auto-close. ``window.close()``
|
||||
// only succeeds when the tab was JS-opened (Chrome --app windows
|
||||
// qualify; regular browser tabs do not) — the button click counts
|
||||
// as a user gesture and gives one more chance. A short timeout
|
||||
// reveals a Ctrl+W hint if the browser still refused.
|
||||
function buildOverlay(doc) {
|
||||
var overlay = doc.createElement('div');
|
||||
overlay.id = 'datatools-farewell-overlay';
|
||||
@@ -140,22 +147,47 @@ _FAREWELL_SCRIPT_TEMPLATE = """
|
||||
'border-radius:12px;background:#161922;max-width:480px;">' +
|
||||
'<h1 style="margin:0 0 8px 0;font-weight:600;letter-spacing:-0.01em;">' +
|
||||
'__TITLE__</h1>' +
|
||||
'<p style="opacity:0.7;margin:0;">__SUBTITLE__</p>' +
|
||||
'<p style="opacity:0.7;margin:0 0 20px 0;">__SUBTITLE__</p>' +
|
||||
'<button id="datatools-close-btn" style="' +
|
||||
'background:#6ee7b7;color:#052e1a;font-weight:600;' +
|
||||
'padding:10px 20px;border-radius:8px;border:none;' +
|
||||
'font-size:15px;cursor:pointer;font-family:inherit;">' +
|
||||
'__CLOSE_BTN__</button>' +
|
||||
'<p id="datatools-close-hint" style="' +
|
||||
'display:none;font-size:13px;opacity:0.6;margin:14px 0 0 0;">' +
|
||||
'__CLOSE_HINT__</p>' +
|
||||
'</div>';
|
||||
return overlay;
|
||||
}
|
||||
function wireClose(doc, win) {
|
||||
var btn = doc.getElementById('datatools-close-btn');
|
||||
if (!btn) return;
|
||||
btn.onclick = function () {
|
||||
try { win.close(); } catch (e) {}
|
||||
try { win.top.close(); } catch (e) {}
|
||||
// If after 250ms the window is still here, the browser
|
||||
// blocked the close — show the manual-close hint.
|
||||
setTimeout(function () {
|
||||
var hint = doc.getElementById('datatools-close-hint');
|
||||
if (hint) hint.style.display = 'block';
|
||||
}, 250);
|
||||
};
|
||||
}
|
||||
try {
|
||||
var doc = window.top.document;
|
||||
var win = window.top;
|
||||
if (!doc.getElementById('datatools-farewell-overlay')) {
|
||||
doc.body.appendChild(buildOverlay(doc));
|
||||
}
|
||||
// Try to close the tab outright too — browsers only honour this
|
||||
// for tabs a script opened, so it usually no-ops, but it's free.
|
||||
try { window.top.close(); } catch (e) {}
|
||||
wireClose(doc, win);
|
||||
// Try to close the tab outright too — succeeds in Chrome --app
|
||||
// windows, no-ops on regular tabs.
|
||||
try { win.close(); } catch (e) {}
|
||||
} catch (e) {
|
||||
// Cross-origin access denied (shouldn't happen given Streamlit's
|
||||
// sandbox flags, but fall back gracefully): cover this iframe.
|
||||
document.body.appendChild(buildOverlay(document));
|
||||
wireClose(document, window);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -184,6 +216,8 @@ def _farewell_script() -> str:
|
||||
_FAREWELL_SCRIPT_TEMPLATE
|
||||
.replace("__TITLE__", _js_html_safe(_t("quit.farewell_title")))
|
||||
.replace("__SUBTITLE__", _js_html_safe(_t("quit.farewell_subtitle")))
|
||||
.replace("__CLOSE_BTN__", _js_html_safe(_t("quit.close_window_button")))
|
||||
.replace("__CLOSE_HINT__", _js_html_safe(_t("quit.close_hint")))
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user