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
|
// We don't navigate to a data: URL here because Chrome blocks
|
||||||
// top-frame navigation to data: URLs (anti-phishing, Chrome 60+).
|
// 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) {
|
function buildOverlay(doc) {
|
||||||
var overlay = doc.createElement('div');
|
var overlay = doc.createElement('div');
|
||||||
overlay.id = 'datatools-farewell-overlay';
|
overlay.id = 'datatools-farewell-overlay';
|
||||||
@@ -140,22 +147,47 @@ _FAREWELL_SCRIPT_TEMPLATE = """
|
|||||||
'border-radius:12px;background:#161922;max-width:480px;">' +
|
'border-radius:12px;background:#161922;max-width:480px;">' +
|
||||||
'<h1 style="margin:0 0 8px 0;font-weight:600;letter-spacing:-0.01em;">' +
|
'<h1 style="margin:0 0 8px 0;font-weight:600;letter-spacing:-0.01em;">' +
|
||||||
'__TITLE__</h1>' +
|
'__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>';
|
'</div>';
|
||||||
return overlay;
|
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 {
|
try {
|
||||||
var doc = window.top.document;
|
var doc = window.top.document;
|
||||||
|
var win = window.top;
|
||||||
if (!doc.getElementById('datatools-farewell-overlay')) {
|
if (!doc.getElementById('datatools-farewell-overlay')) {
|
||||||
doc.body.appendChild(buildOverlay(doc));
|
doc.body.appendChild(buildOverlay(doc));
|
||||||
}
|
}
|
||||||
// Try to close the tab outright too — browsers only honour this
|
wireClose(doc, win);
|
||||||
// for tabs a script opened, so it usually no-ops, but it's free.
|
// Try to close the tab outright too — succeeds in Chrome --app
|
||||||
try { window.top.close(); } catch (e) {}
|
// windows, no-ops on regular tabs.
|
||||||
|
try { win.close(); } catch (e) {}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cross-origin access denied (shouldn't happen given Streamlit's
|
// Cross-origin access denied (shouldn't happen given Streamlit's
|
||||||
// sandbox flags, but fall back gracefully): cover this iframe.
|
// sandbox flags, but fall back gracefully): cover this iframe.
|
||||||
document.body.appendChild(buildOverlay(document));
|
document.body.appendChild(buildOverlay(document));
|
||||||
|
wireClose(document, window);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
@@ -184,6 +216,8 @@ def _farewell_script() -> str:
|
|||||||
_FAREWELL_SCRIPT_TEMPLATE
|
_FAREWELL_SCRIPT_TEMPLATE
|
||||||
.replace("__TITLE__", _js_html_safe(_t("quit.farewell_title")))
|
.replace("__TITLE__", _js_html_safe(_t("quit.farewell_title")))
|
||||||
.replace("__SUBTITLE__", _js_html_safe(_t("quit.farewell_subtitle")))
|
.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")))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,9 @@
|
|||||||
"button": "Quit app",
|
"button": "Quit app",
|
||||||
"shutting_down": "Shutting down… you can close this window.",
|
"shutting_down": "Shutting down… you can close this window.",
|
||||||
"farewell_title": "DataTools has shut down",
|
"farewell_title": "DataTools has shut down",
|
||||||
"farewell_subtitle": "You can close this window."
|
"farewell_subtitle": "You can close this window.",
|
||||||
|
"close_window_button": "Close this window",
|
||||||
|
"close_hint": "Your browser blocked the close. Press Ctrl+W (or ⌘W on Mac) to close this tab manually."
|
||||||
},
|
},
|
||||||
"close_page": {
|
"close_page": {
|
||||||
"page_title": "DataTools — Close",
|
"page_title": "DataTools — Close",
|
||||||
|
|||||||
@@ -51,7 +51,9 @@
|
|||||||
"button": "Cerrar app",
|
"button": "Cerrar app",
|
||||||
"shutting_down": "Cerrando… ya puedes cerrar esta ventana.",
|
"shutting_down": "Cerrando… ya puedes cerrar esta ventana.",
|
||||||
"farewell_title": "DataTools se ha cerrado",
|
"farewell_title": "DataTools se ha cerrado",
|
||||||
"farewell_subtitle": "Ya puedes cerrar esta ventana."
|
"farewell_subtitle": "Ya puedes cerrar esta ventana.",
|
||||||
|
"close_window_button": "Cerrar esta ventana",
|
||||||
|
"close_hint": "Tu navegador bloqueó el cierre. Presiona Ctrl+W (o ⌘W en Mac) para cerrar esta pestaña manualmente."
|
||||||
},
|
},
|
||||||
"close_page": {
|
"close_page": {
|
||||||
"page_title": "DataTools — Cerrar",
|
"page_title": "DataTools — Cerrar",
|
||||||
|
|||||||
Reference in New Issue
Block a user