mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:44:49 +08:00
UX: Add loading indicator when loading 'new or updated topics' (#25649)
Also improves error handling so that the action can be retried if the network request fails
This commit is contained in:
parent
06bbed69f9
commit
9883e6a0c8
|
@ -42,19 +42,26 @@
|
|||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if this.topicTrackingState.hasIncoming}}
|
||||
{{#if (or this.topicTrackingState.hasIncoming @model.loadingBefore)}}
|
||||
<div class="show-more {{if this.hasTopics 'has-topics'}}">
|
||||
<a
|
||||
tabindex="0"
|
||||
href
|
||||
{{on "click" this.showInserted}}
|
||||
class="alert alert-info clickable"
|
||||
class="alert alert-info clickable
|
||||
{{if @model.loadingBefore 'loading'}}"
|
||||
>
|
||||
<CountI18n
|
||||
@key="topic_count_"
|
||||
@suffix={{this.topicTrackingState.filter}}
|
||||
@count={{this.topicTrackingState.incomingCount}}
|
||||
@count={{or
|
||||
@model.loadingBefore
|
||||
this.topicTrackingState.incomingCount
|
||||
}}
|
||||
/>
|
||||
{{#if @model.loadingBefore}}
|
||||
{{loading-spinner size="small"}}
|
||||
{{/if}}
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import DismissNew from "discourse/components/modal/dismiss-new";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { filterTypeForMode } from "discourse/lib/filter-mode";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import Topic from "discourse/models/topic";
|
||||
|
@ -15,6 +17,8 @@ export default class DiscoveryTopics extends Component {
|
|||
@service topicTrackingState;
|
||||
@service site;
|
||||
|
||||
@tracked loadingNew;
|
||||
|
||||
get redirectedReason() {
|
||||
return this.currentUser?.user_option.redirected_to_top?.reason;
|
||||
}
|
||||
|
@ -56,7 +60,7 @@ export default class DiscoveryTopics extends Component {
|
|||
dismissTopics = false,
|
||||
untrack = false
|
||||
) {
|
||||
const tracked =
|
||||
const isTracked =
|
||||
(this.router.currentRoute.queryParams["f"] ||
|
||||
this.router.currentRoute.queryParams["filter"]) === "tracked";
|
||||
|
||||
|
@ -65,7 +69,7 @@ export default class DiscoveryTopics extends Component {
|
|||
this.args.category,
|
||||
!this.args.noSubcategories,
|
||||
{
|
||||
tracked,
|
||||
tracked: isTracked,
|
||||
tag: this.args.tag,
|
||||
topicIds,
|
||||
dismissPosts,
|
||||
|
@ -99,13 +103,22 @@ export default class DiscoveryTopics extends Component {
|
|||
|
||||
// Show newly inserted topics
|
||||
@action
|
||||
showInserted(event) {
|
||||
async showInserted(event) {
|
||||
event?.preventDefault();
|
||||
const tracker = this.topicTrackingState;
|
||||
|
||||
// Move inserted into topics
|
||||
this.args.model.loadBefore(tracker.get("newIncoming"), true);
|
||||
tracker.resetTracking();
|
||||
if (this.args.model.loadingBefore) {
|
||||
return; // Already loading
|
||||
}
|
||||
|
||||
const { topicTrackingState } = this;
|
||||
|
||||
try {
|
||||
const topicIds = [...topicTrackingState.newIncoming];
|
||||
await this.args.model.loadBefore(topicIds, true);
|
||||
topicTrackingState.clearIncoming(topicIds);
|
||||
} catch (e) {
|
||||
popupAjaxError(e);
|
||||
}
|
||||
}
|
||||
|
||||
get showTopicsAndRepliesToggle() {
|
||||
|
|
|
@ -57,6 +57,16 @@ export default Component.extend({
|
|||
this.renderTopicListItem();
|
||||
},
|
||||
|
||||
// Already-rendered topic is marked as highlighted
|
||||
// Ideally this should be a modifier... but we can't do that
|
||||
// until this component has its tagName removed.
|
||||
@observes("topic.highlight")
|
||||
topicHighlightChanged() {
|
||||
if (this.topic.highlight) {
|
||||
this._highlightIfNeeded();
|
||||
}
|
||||
},
|
||||
|
||||
@observes("topic.pinned", "expandGloballyPinned", "expandAllPinned")
|
||||
renderTopicListItem() {
|
||||
const template = findRawTemplate("list/topic-list-item");
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import EmberObject from "@ember/object";
|
||||
import { notEmpty } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
@ -121,6 +122,7 @@ export default class TopicList extends RestModel {
|
|||
|
||||
@service session;
|
||||
|
||||
@tracked loadingBefore = false;
|
||||
@notEmpty("more_topics_url") canLoadMore;
|
||||
|
||||
forEachNew(topics, callback) {
|
||||
|
@ -210,15 +212,19 @@ export default class TopicList extends RestModel {
|
|||
}
|
||||
|
||||
// loads topics with these ids "before" the current topics
|
||||
loadBefore(topic_ids, storeInSession) {
|
||||
// refresh dupes
|
||||
this.topics.removeObjects(
|
||||
this.topics.filter((topic) => topic_ids.includes(topic.id))
|
||||
);
|
||||
async loadBefore(topic_ids, storeInSession) {
|
||||
this.loadingBefore = topic_ids.length;
|
||||
|
||||
const url = `/${this.filter}.json?topic_ids=${topic_ids.join(",")}`;
|
||||
try {
|
||||
const url = `/${this.filter}.json?topic_ids=${topic_ids.join(",")}`;
|
||||
|
||||
const result = await ajax({ url, data: this.params });
|
||||
|
||||
// refresh dupes
|
||||
this.topics.removeObjects(
|
||||
this.topics.filter((topic) => topic_ids.includes(topic.id))
|
||||
);
|
||||
|
||||
return ajax({ url, data: this.params }).then((result) => {
|
||||
let i = 0;
|
||||
this.forEachNew(TopicList.topicsFrom(this.store, result), (t) => {
|
||||
// highlight the first of the new topics so we can get a visual feedback
|
||||
|
@ -230,6 +236,8 @@ export default class TopicList extends RestModel {
|
|||
if (storeInSession) {
|
||||
this.session.set("topicList", this);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
this.loadingBefore = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -335,6 +335,19 @@ export default class TopicTrackingState extends EmberObject {
|
|||
this.set("incomingCount", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given topic IDs from the list of incoming topics.
|
||||
*
|
||||
* @method clearIncoming
|
||||
*/
|
||||
clearIncoming(topicIds) {
|
||||
const toRemove = new Set(topicIds);
|
||||
this.newIncoming = this.newIncoming.filter(
|
||||
(topicId) => !toRemove.has(topicId)
|
||||
);
|
||||
this.set("incomingCount", this.newIncoming.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track how many new topics came for the specified filter.
|
||||
*
|
||||
|
|
|
@ -178,6 +178,13 @@
|
|||
.alert {
|
||||
margin: 0;
|
||||
padding: 1.1em 2em 1.1em 0.65em;
|
||||
gap: 0.65em;
|
||||
align-items: center;
|
||||
|
||||
&.loading {
|
||||
color: var(--primary-medium);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -574,3 +574,11 @@ td .main-link {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#list-area .show-more .alert {
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
&.loading {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user