mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
popup_banners: Redesign connection banner to use new banner component.
This commit serves as the base commit for redesigning the alert banners by migrating them to use the new banner component. We use a new name to refer to these banners — "Popup banners", which is more descriptive about their behavior. The Popup banners are appended to the container in a stacking order, i.e., the most recent popup banner appears on the top and the oldest one is sent to the bottom of the stack. These banners also inherit the animations from the alert banners for visual appeal. This commit also fixes the bug where clicking on the "Try now" button in the popup banner resulting from an error in the `/json/messages` endpoint resulted in call to restart_get_events in server_events.js instead of load_messages in message_fetch.ts. Fixes #31282.
This commit is contained in:
@@ -180,14 +180,7 @@
|
||||
<source class="notification-sound-source-mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
|
||||
<div class="alert-box">
|
||||
<div class="alert alert_sidebar alert-error home-error-bar" id="connection-error">
|
||||
<div class="exit"></div>
|
||||
<strong class="message">{{ _('Unable to connect to Zulip.') }}</strong>
|
||||
{{ _('Updates may be delayed.') }}
|
||||
{{ _('Retrying soon…') }}
|
||||
<a class="restart_get_events_button">{{ _('Try now.') }}</a>
|
||||
</div>
|
||||
<div id="popup_banners_wrapper" class="banner-wrapper alert-box">
|
||||
<div class="alert alert_sidebar alert-error home-error-bar" id="zephyr-mirror-error">
|
||||
<div class="exit"></div>
|
||||
{# The below isn't tagged for translation
|
||||
|
@@ -182,6 +182,7 @@ EXEMPT_FILES = make_set(
|
||||
"web/src/popover_menus.ts",
|
||||
"web/src/popover_menus_data.ts",
|
||||
"web/src/popovers.ts",
|
||||
"web/src/popup_banners.ts",
|
||||
"web/src/read_receipts.ts",
|
||||
"web/src/realm_icon.ts",
|
||||
"web/src/realm_logo.ts",
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import $ from "jquery";
|
||||
import assert from "minimalistic-assert";
|
||||
import {z} from "zod";
|
||||
|
||||
@@ -22,12 +21,12 @@ import * as message_viewport from "./message_viewport.ts";
|
||||
import * as narrow_banner from "./narrow_banner.ts";
|
||||
import {page_params} from "./page_params.ts";
|
||||
import * as people from "./people.ts";
|
||||
import * as popup_banners from "./popup_banners.ts";
|
||||
import * as recent_view_ui from "./recent_view_ui.ts";
|
||||
import type {NarrowTerm} from "./state_data.ts";
|
||||
import {narrow_term_schema} from "./state_data.ts";
|
||||
import * as stream_data from "./stream_data.ts";
|
||||
import * as stream_list from "./stream_list.ts";
|
||||
import * as ui_report from "./ui_report.ts";
|
||||
import * as util from "./util.ts";
|
||||
|
||||
const response_schema = z.object({
|
||||
@@ -392,18 +391,16 @@ export function load_messages(opts: MessageFetchOptions, attempt = 1): void {
|
||||
url: "/json/messages",
|
||||
data,
|
||||
success(raw_data) {
|
||||
if (!$("#connection-error").hasClass("get-events-error")) {
|
||||
ui_report.hide_error($("#connection-error"));
|
||||
}
|
||||
popup_banners.close_connection_error_popup_banner(true);
|
||||
const data = response_schema.parse(raw_data);
|
||||
get_messages_success(data, opts);
|
||||
},
|
||||
error(xhr) {
|
||||
if (xhr.status === 400 && !$("#connection-error").hasClass("get-events-error")) {
|
||||
if (xhr.status === 400) {
|
||||
// We successfully reached the server, so hide the
|
||||
// connection error notice, even if the request failed
|
||||
// for other reasons.
|
||||
ui_report.hide_error($("#connection-error"));
|
||||
popup_banners.close_connection_error_popup_banner(true);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -443,7 +440,11 @@ export function load_messages(opts: MessageFetchOptions, attempt = 1): void {
|
||||
return;
|
||||
}
|
||||
|
||||
ui_report.show_error($("#connection-error"));
|
||||
popup_banners.open_connection_error_popup_banner({
|
||||
on_retry_callback() {
|
||||
load_messages(opts, attempt + 1);
|
||||
},
|
||||
});
|
||||
|
||||
const delay_secs = util.get_retry_backoff_seconds(xhr, attempt, true);
|
||||
setTimeout(() => {
|
||||
|
79
web/src/popup_banners.ts
Normal file
79
web/src/popup_banners.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import * as banners from "./banners.ts";
|
||||
import type {Banner} from "./banners.ts";
|
||||
import {$t} from "./i18n.ts";
|
||||
|
||||
const CONNECTION_ERROR_POPUP_BANNER: Banner = {
|
||||
intent: "danger",
|
||||
label: $t({
|
||||
defaultMessage: "Unable to connect to Zulip. Retrying soon…",
|
||||
}),
|
||||
buttons: [
|
||||
{
|
||||
type: "quiet",
|
||||
label: $t({defaultMessage: "Try now"}),
|
||||
custom_classes: "retry-connection",
|
||||
},
|
||||
],
|
||||
close_button: true,
|
||||
custom_classes: "connection-error-banner popup-banner",
|
||||
};
|
||||
|
||||
export function open_connection_error_popup_banner(opts: {
|
||||
on_retry_callback: () => void;
|
||||
is_get_events_error?: boolean;
|
||||
}): void {
|
||||
// If the banner is already open, don't open it again.
|
||||
if ($("#popup_banners_wrapper").find(".connection-error-banner").length > 0) {
|
||||
return;
|
||||
}
|
||||
// Prevent the interference between the server errors from
|
||||
// get_events in web/src/server_events.js and the one from
|
||||
// load_messages in web/src/message_fetch.ts.
|
||||
if (opts.is_get_events_error) {
|
||||
CONNECTION_ERROR_POPUP_BANNER.custom_classes += " get-events-error";
|
||||
}
|
||||
banners.append(CONNECTION_ERROR_POPUP_BANNER, $("#popup_banners_wrapper"));
|
||||
|
||||
$("#popup_banners_wrapper").on("click", ".retry-connection", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
opts.on_retry_callback();
|
||||
});
|
||||
}
|
||||
|
||||
export function close_connection_error_popup_banner(check_if_get_events_error = false): void {
|
||||
const $banner = $("#popup_banners_wrapper").find(".connection-error-banner");
|
||||
if ($banner.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (check_if_get_events_error && $banner.hasClass("get-events-error")) {
|
||||
return;
|
||||
}
|
||||
$banner.addClass("fade-out");
|
||||
// The delay is the same as the animation duration for fade-out.
|
||||
setTimeout(() => {
|
||||
banners.close($banner);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
export function initialize(): void {
|
||||
$("#popup_banners_wrapper").on(
|
||||
"click",
|
||||
".banner-close-action",
|
||||
function (this: HTMLElement, e) {
|
||||
// Override the banner close event listener in web/src/banners.ts,
|
||||
// to add a fade-out animation when the banner is closed.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const $banner = $(this).closest(".banner");
|
||||
$banner.addClass("fade-out");
|
||||
// The delay is the same as the animation duration for fade-out.
|
||||
setTimeout(() => {
|
||||
banners.close($banner);
|
||||
}, 300);
|
||||
},
|
||||
);
|
||||
}
|
@@ -7,12 +7,12 @@ import * as echo from "./echo.ts";
|
||||
import * as loading from "./loading.ts";
|
||||
import * as message_events from "./message_events.ts";
|
||||
import {page_params} from "./page_params.ts";
|
||||
import * as popup_banners from "./popup_banners.ts";
|
||||
import * as reload from "./reload.ts";
|
||||
import * as reload_state from "./reload_state.ts";
|
||||
import * as sent_messages from "./sent_messages.ts";
|
||||
import * as server_events_dispatch from "./server_events_dispatch.js";
|
||||
import {server_message_schema} from "./server_message.ts";
|
||||
import * as ui_report from "./ui_report.ts";
|
||||
import * as util from "./util.ts";
|
||||
import * as watchdog from "./watchdog.ts";
|
||||
|
||||
@@ -146,16 +146,6 @@ function get_events_success(events) {
|
||||
}
|
||||
}
|
||||
|
||||
function show_ui_connection_error() {
|
||||
ui_report.show_error($("#connection-error"));
|
||||
$("#connection-error").addClass("get-events-error");
|
||||
}
|
||||
|
||||
function hide_ui_connection_error() {
|
||||
ui_report.hide_error($("#connection-error"));
|
||||
$("#connection-error").removeClass("get-events-error");
|
||||
}
|
||||
|
||||
function get_events({dont_block = false} = {}) {
|
||||
if (reload_state.is_in_progress()) {
|
||||
return;
|
||||
@@ -202,7 +192,7 @@ function get_events({dont_block = false} = {}) {
|
||||
try {
|
||||
get_events_xhr = undefined;
|
||||
get_events_failures = 0;
|
||||
hide_ui_connection_error();
|
||||
popup_banners.close_connection_error_popup_banner();
|
||||
|
||||
get_events_success(data.events);
|
||||
} catch (error) {
|
||||
@@ -230,15 +220,20 @@ function get_events({dont_block = false} = {}) {
|
||||
} else if (error_type === "timeout") {
|
||||
// Retry indefinitely on timeout.
|
||||
get_events_failures = 0;
|
||||
hide_ui_connection_error();
|
||||
popup_banners.close_connection_error_popup_banner();
|
||||
} else {
|
||||
get_events_failures += 1;
|
||||
}
|
||||
|
||||
if (get_events_failures >= 8) {
|
||||
show_ui_connection_error();
|
||||
popup_banners.open_connection_error_popup_banner({
|
||||
on_retry_callback() {
|
||||
restart_get_events({dont_block: true});
|
||||
},
|
||||
is_get_events_error: true,
|
||||
});
|
||||
} else {
|
||||
hide_ui_connection_error();
|
||||
popup_banners.close_connection_error_popup_banner();
|
||||
}
|
||||
} catch (error) {
|
||||
blueslip.error("Failed to handle get_events error", undefined, error);
|
||||
@@ -287,9 +282,6 @@ export function initialize(params) {
|
||||
get_events_failures = 0;
|
||||
restart_get_events({dont_block: true});
|
||||
});
|
||||
$(".restart_get_events_button").on("click", () => {
|
||||
restart_get_events({dont_block: true});
|
||||
});
|
||||
|
||||
get_events();
|
||||
}
|
||||
|
@@ -90,6 +90,7 @@ import * as pm_conversations from "./pm_conversations.ts";
|
||||
import * as pm_list from "./pm_list.ts";
|
||||
import * as popover_menus from "./popover_menus.ts";
|
||||
import * as popovers from "./popovers.ts";
|
||||
import * as popup_banners from "./popup_banners.ts";
|
||||
import * as presence from "./presence.ts";
|
||||
import * as pygments_data from "./pygments_data.ts";
|
||||
import * as realm_logo from "./realm_logo.ts";
|
||||
@@ -525,6 +526,7 @@ export function initialize_everything(state_data) {
|
||||
message_viewport.initialize();
|
||||
banners.initialize();
|
||||
navbar_alerts.initialize();
|
||||
popup_banners.initialize();
|
||||
message_list_hover.initialize();
|
||||
initialize_kitchen_sink_stuff();
|
||||
local_message.initialize(state_data.local_message);
|
||||
|
@@ -12,6 +12,9 @@
|
||||
}
|
||||
|
||||
.alert-animations {
|
||||
/* TODO: Remove these animations in favour of those
|
||||
in web/styles/banners.css, once all the alert popups
|
||||
have been converted to use the new banner component. */
|
||||
&.show {
|
||||
animation-name: fadeIn;
|
||||
animation-duration: 0.3s;
|
||||
|
@@ -145,3 +145,55 @@
|
||||
color: var(--color-text-danger-banner);
|
||||
border-color: var(--color-border-danger-banner);
|
||||
}
|
||||
|
||||
@keyframes popup-banner-fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(
|
||||
calc(-1 * var(--popup-banner-translate-y-distance))
|
||||
);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popup-banner-fadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(
|
||||
calc(-1 * var(--popup-banner-translate-y-distance))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.popup-banner-animations {
|
||||
animation-name: popup-banner-fadeIn;
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
|
||||
&.fade-out {
|
||||
animation-name: popup-banner-fadeOut;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-banner {
|
||||
@extend .popup-banner-animations;
|
||||
|
||||
pointer-events: auto;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
|
||||
/* Here the container width == viewport width,
|
||||
so we can work with the media breakpoints. */
|
||||
@container banner (width < $lg_min) {
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
@@ -23,10 +23,8 @@ page_params.test_suite = false;
|
||||
// we also directly write to pointer
|
||||
set_global("pointer", {});
|
||||
|
||||
mock_esm("../src/ui_report", {
|
||||
hide_error() {
|
||||
return false;
|
||||
},
|
||||
mock_esm("../src/popup_banners", {
|
||||
close_connection_error_popup_banner() {},
|
||||
});
|
||||
|
||||
mock_esm("../src/stream_events", {
|
||||
|
Reference in New Issue
Block a user