FEATURE: Offline indicator (#21369)
This commit is contained in:
parent
704a792f18
commit
3bfc6805ce
app/assets
javascripts/discourse/app
components
initializers
services
templates
stylesheets/common/components
config
spec/system
|
@ -0,0 +1,10 @@
|
|||
{{#if this.showing}}
|
||||
<div class="offline-indicator">
|
||||
<span>{{i18n "offline_indicator.no_internet"}}</span>
|
||||
<DButton
|
||||
@label="offline_indicator.refresh_page"
|
||||
@display="link"
|
||||
@action={{this.refresh}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -0,0 +1,20 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class OfflineIndicator extends Component {
|
||||
@service messageBusConnectivity;
|
||||
@service siteSettings;
|
||||
|
||||
get showing() {
|
||||
return (
|
||||
this.siteSettings.enable_offline_indicator &&
|
||||
!this.messageBusConnectivity.connected
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
refresh() {
|
||||
window.location.reload(true);
|
||||
}
|
||||
}
|
|
@ -5,23 +5,17 @@ import { handleLogoff } from "discourse/lib/ajax";
|
|||
import userPresent, { onPresenceChange } from "discourse/lib/user-presence";
|
||||
|
||||
const LONG_POLL_AFTER_UNSEEN_TIME = 1200000; // 20 minutes
|
||||
const CONNECTIVITY_ERROR_CLASS = "message-bus-offline";
|
||||
|
||||
function updateConnectivityIndicator(stat) {
|
||||
if (stat === "error") {
|
||||
document.documentElement.classList.add(CONNECTIVITY_ERROR_CLASS);
|
||||
} else {
|
||||
document.documentElement.classList.remove(CONNECTIVITY_ERROR_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
function ajax(opts) {
|
||||
function ajax(opts, messageBusConnectivity, appState) {
|
||||
if (opts.complete) {
|
||||
const oldComplete = opts.complete;
|
||||
opts.complete = function (xhr, stat) {
|
||||
handleLogoff(xhr);
|
||||
oldComplete(xhr, stat);
|
||||
updateConnectivityIndicator(stat);
|
||||
|
||||
messageBusConnectivity.setConnectivity(
|
||||
xhr.readyState === 4 || stat === "abort" || appState.background
|
||||
);
|
||||
};
|
||||
} else {
|
||||
opts.complete = handleLogoff;
|
||||
|
@ -42,7 +36,11 @@ export default {
|
|||
|
||||
const messageBus = container.lookup("service:message-bus"),
|
||||
user = container.lookup("service:current-user"),
|
||||
siteSettings = container.lookup("service:site-settings");
|
||||
siteSettings = container.lookup("service:site-settings"),
|
||||
appState = container.lookup("service:app-state"),
|
||||
messageBusConnectivity = container.lookup(
|
||||
"service:message-bus-connectivity"
|
||||
);
|
||||
|
||||
messageBus.alwaysLongPoll = !isProduction();
|
||||
messageBus.shouldLongPollCallback = () =>
|
||||
|
@ -97,7 +95,7 @@ export default {
|
|||
if (userPresent()) {
|
||||
opts.headers["Discourse-Present"] = "true";
|
||||
}
|
||||
return ajax(opts);
|
||||
return ajax(opts, messageBusConnectivity, appState);
|
||||
};
|
||||
} else {
|
||||
messageBus.ajax = function (opts) {
|
||||
|
@ -105,7 +103,7 @@ export default {
|
|||
if (userPresent()) {
|
||||
opts.headers["Discourse-Present"] = "true";
|
||||
}
|
||||
return ajax(opts);
|
||||
return ajax(opts, messageBusConnectivity, appState);
|
||||
};
|
||||
|
||||
messageBus.baseUrl = getURL("/");
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import Service, { inject as service } from "@ember/service";
|
||||
|
||||
export default class AppState extends Service {
|
||||
@service capabilities;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
if (this.capabilities.isAppWebview) {
|
||||
window.addEventListener("AppStateChange", (event) => {
|
||||
// Possible states: "active", "inactive", and "background"
|
||||
this._state = event.detail?.newAppState;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get active() {
|
||||
return this._state === "active";
|
||||
}
|
||||
|
||||
get inactive() {
|
||||
return this._state === "inactive";
|
||||
}
|
||||
|
||||
get background() {
|
||||
return this._state === "background";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import Service from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
const CONNECTIVITY_ERROR_CLASS = "message-bus-offline";
|
||||
|
||||
export default class MessageBusConnectivity extends Service {
|
||||
@tracked connected = true;
|
||||
|
||||
setConnectivity(connected) {
|
||||
this.connected = connected;
|
||||
document.documentElement.classList.toggle(
|
||||
CONNECTIVITY_ERROR_CLASS,
|
||||
!connected
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
{{/if}}
|
||||
|
||||
<SoftwareUpdatePrompt />
|
||||
<OfflineIndicator />
|
||||
|
||||
<PluginOutlet
|
||||
@name="below-site-header"
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
@import "ignored-user-list";
|
||||
@import "keyboard_shortcuts";
|
||||
@import "navs";
|
||||
@import "offline-indicator";
|
||||
@import "pick-files-button";
|
||||
@import "relative-time-picker";
|
||||
@import "share-and-invite-modal";
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.offline-indicator {
|
||||
position: fixed;
|
||||
top: var(--header-offset);
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
padding: 0.5em 1em;
|
||||
background-color: var(--primary-very-low);
|
||||
border-radius: var(--d-nav-pill-border-radius);
|
||||
z-index: z("header") - 10;
|
||||
}
|
|
@ -2070,6 +2070,10 @@ en:
|
|||
intro: "Hello! Looks like you’re enjoying the discussion, but you haven’t signed up for an account yet."
|
||||
value_prop: "Tired of scrolling through the same posts? When you create an account you’ll always come back to where you left off. With an account you can also be notified of new replies, save bookmarks, and use likes to thank others. We can all work together to make this community great. :heart:"
|
||||
|
||||
offline_indicator:
|
||||
no_internet: "No internet connection."
|
||||
refresh_page: "Refresh page"
|
||||
|
||||
summary:
|
||||
enabled_description: "You're viewing a summary of this topic: the most interesting posts as determined by the community."
|
||||
description:
|
||||
|
|
|
@ -2343,6 +2343,7 @@ en:
|
|||
default_text_size: "Text size which is selected by default"
|
||||
|
||||
default_title_count_mode: "Default mode for the page title counter"
|
||||
enable_offline_indicator: "Display a message to users when it is detected that they have no network connection"
|
||||
|
||||
retain_web_hook_events_period_days: "Number of days to retain web hook event records."
|
||||
retry_web_hook_events: "Automatically retry failed web hook events for 4 times. Time gaps between the retries are 1, 5, 25 and 125 minutes."
|
||||
|
|
|
@ -341,7 +341,7 @@ basic:
|
|||
list_type: compact
|
||||
default: "14"
|
||||
allow_any: false
|
||||
refresh: true
|
||||
refresh: true
|
||||
enable_bookmarks_with_reminders:
|
||||
client: true
|
||||
default: true
|
||||
|
@ -2701,6 +2701,9 @@ user_preferences:
|
|||
choices:
|
||||
- notifications
|
||||
- contextual
|
||||
enable_offline_indicator:
|
||||
default: false
|
||||
client: true
|
||||
api:
|
||||
retain_web_hook_events_period_days:
|
||||
default: 30
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe "Network Disconnected", type: :system, js: true do
|
||||
fab!(:current_user) { Fabricate(:user) }
|
||||
|
||||
def with_network_disconnected
|
||||
page.driver.browser.network_conditions = { offline: true }
|
||||
yield
|
||||
page.driver.browser.network_conditions = { offline: false }
|
||||
end
|
||||
|
||||
it "Message bus connectivity service adds class to DOM and displays offline indicator" do
|
||||
SiteSetting.enable_offline_indicator = true
|
||||
|
||||
visit("/c")
|
||||
|
||||
expect(page).to have_no_css("html.message-bus-offline")
|
||||
expect(page).to have_no_css(".offline-indicator")
|
||||
|
||||
with_network_disconnected do
|
||||
# Message bus connectivity services adds the disconnected class to the DOM
|
||||
expect(page).to have_css("html.message-bus-offline")
|
||||
|
||||
# Offline indicator is rendered
|
||||
expect(page).to have_css(".offline-indicator")
|
||||
end
|
||||
end
|
||||
|
||||
it "Doesn't show the offline indicator when the site setting isn't present" do
|
||||
SiteSetting.enable_offline_indicator = false
|
||||
|
||||
visit("/c")
|
||||
|
||||
with_network_disconnected do
|
||||
expect(page).to have_css("html.message-bus-offline")
|
||||
expect(page).not_to have_css(".offline-indicator")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue