Linked label dropdown in members screen to paginated list loading

no issue

- moved model loading back into the route
- updated model loading to refresh correctly when `label` query param changes
- fixed infinite loading/"no members" display in members list by using the `members.loading` property that `ella-sparse` gives us (previously we'd never leave the loading display because `this.members.length` would be 0)
- changed the members nav link to reset query params only if it's clicked whilst on the members screen - matches posts/pages behaviour and lets you navigate without having to re-enter your filter/search
This commit is contained in:
Kevin Ansfield 2020-05-22 12:47:03 +01:00
parent f36ebb9a24
commit ad7c8ed423
5 changed files with 74 additions and 79 deletions

View File

@ -61,7 +61,7 @@
<li>
{{!-- clicking the Content link whilst on the content screen should reset the filter --}}
{{#if (eq this.router.currentRouteName "pages")}}
<LinkTo @route="pages" @query={{hash type=null author=null tag=null order=null}} @classNames="active" data-test-nav="pages">{{svg-jar "page"}}Pages</LinkTo>
<LinkTo @route="pages" @query={{reset-query-params "pages"}} @classNames="active" data-test-nav="pages">{{svg-jar "page"}}Pages</LinkTo>
{{else}}
<LinkTo @route="pages" data-test-nav="pages">{{svg-jar "page"}}Pages</LinkTo>
{{/if}}
@ -75,7 +75,11 @@
</li>
{{#if this.config.enableDeveloperExperiments}}
<li>
<LinkTo @route="members" @current-when="members member" @query={{hash label=null}} data-test-nav="members-old">{{svg-jar "members"}}Members (dev)</LinkTo>
{{#if (eq this.router.currentRouteName "members.index")}}
<LinkTo @route="members" @current-when="members member" @query={{reset-query-params "members.index"}} data-test-nav="members-old">{{svg-jar "members"}}Members (dev)</LinkTo>
{{else}}
<LinkTo @route="members" @current-when="members member" data-test-nav="members-old">{{svg-jar "members"}}Members (dev)</LinkTo>
{{/if}}
</li>
{{/if}}
{{/if}}

View File

@ -1,22 +1,22 @@
import Controller from '@ember/controller';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
import moment from 'moment';
import {A} from '@ember/array';
import {action} from '@ember/object';
import {alias} from '@ember/object/computed';
import {pluralize} from 'ember-inflector';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';
export default class MembersController extends Controller {
@service ellaSparse;
@service store;
queryParams = ['label'];
@alias('model') members;
@tracked searchText = '';
@tracked label = null;
@tracked members = null;
@tracked modalLabel = null;
@tracked showLabelModal = false;
@ -24,7 +24,6 @@ export default class MembersController extends Controller {
constructor() {
super(...arguments);
this.members = this.store.peekAll('member');
this._availableLabels = this.store.peekAll('label');
}
@ -32,21 +31,26 @@ export default class MembersController extends Controller {
get listHeader() {
let {searchText, selectedLabel, members} = this;
if (members.loading) {
return 'Loading...';
}
if (searchText) {
return 'Search result';
}
if (this.fetchMembersTask.lastSuccessful) {
let count = pluralize(members.length, 'member');
if (selectedLabel && selectedLabel.slug) {
if (members.length > 1) {
return `${count} match current filter`;
} else {
return `${count} matches current filter`;
}
let count = `${members.length.toLocaleString()} ${pluralize(members.length, 'member', {withoutCount: true})}`;
if (selectedLabel && selectedLabel.slug) {
if (members.length > 1) {
return `${count} match current filter`;
} else {
return `${count} matches current filter`;
}
return count;
}
return 'Loading...';
return count;
}
get showingAll() {
@ -80,32 +84,6 @@ export default class MembersController extends Controller {
};
}
get filteredMembers() {
let {members, searchText, label} = this;
searchText = searchText.toLowerCase();
let filtered = members.filter((member) => {
if (!searchText) {
return true;
}
let {name, email} = member;
return (name && name.toLowerCase().indexOf(searchText) >= 0)
|| (email && email.toLowerCase().indexOf(searchText) >= 0);
}).filter((member) => {
if (!label) {
return true;
}
return !!member.labels.find((_label) => {
return _label.slug === label;
});
}).sort((a, b) => {
return b.createdAtUTC.valueOf() - a.createdAtUTC.valueOf();
});
return filtered;
}
// Actions -----------------------------------------------------------------
@action
@ -161,38 +139,6 @@ export default class MembersController extends Controller {
// Tasks -------------------------------------------------------------------
@task
*fetchMembersTask({forceReload = false} = {}) {
// use a fixed created_at date so that subsequent pages have a consistent index
let startDate = new Date();
// unless we have a forced reload, do not re-fetch the members list unless it's more than a minute old
// keeps navigation between list->details->list snappy
if (!forceReload && this._startDate && !(this._startDate - startDate > 1 * 60 * 1000)) {
return;
}
this._startDate = startDate;
this.members = yield this.ellaSparse.array((range = {}, query = {}) => {
query = Object.assign({
limit: range.length,
page: range.start / range.length,
order: 'created_at desc',
filter: `created_at:<='${moment.utc(this._startDate).format('YYYY-MM-DD HH:mm:ss')}'`
}, query);
return this.store.query('member', query).then((result) => {
return {
data: result,
total: result.meta.pagination.total
};
});
}, {
limit: 50
});
}
@task
*fetchLabelsTask() {
if (!this._hasLoadedLabels) {

View File

@ -7,6 +7,12 @@ export const DEFAULT_QUERY_PARAMS = {
tag: null,
order: null
},
pages: {
type: null,
author: null,
tag: null,
order: null
},
'members.index': {
label: null
}

View File

@ -1,8 +1,11 @@
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
import moment from 'moment';
import {inject as service} from '@ember/service';
export default class MembersRoute extends AuthenticatedRoute {
@service config;
@service ellaSparse;
@service store;
queryParams = {
label: {refreshModel: true}
@ -20,10 +23,46 @@ export default class MembersRoute extends AuthenticatedRoute {
});
}
model(params) {
// use a fixed created_at date so that subsequent pages have a consistent index
let startDate = new Date();
// bypass the stale data shortcut if params change
let forceReload = params.label !== this._lastLabel;
this._lastLabel = params.label;
// unless we have a forced reload, do not re-fetch the members list unless it's more than a minute old
// keeps navigation between list->details->list snappy
if (!forceReload && this._startDate && !(this._startDate - startDate > 1 * 60 * 1000)) {
return this.controller.members;
}
this._startDate = startDate;
return this.ellaSparse.array((range = {}, query = {}) => {
const labelFilter = params.label ? `label:'${params.label}'+` : '';
query = Object.assign({
limit: range.length,
page: range.start / range.length,
order: 'created_at desc',
filter: `${labelFilter}created_at:<='${moment.utc(this._startDate).format('YYYY-MM-DD HH:mm:ss')}'`
}, query);
return this.store.query('member', query).then((result) => {
return {
data: result,
total: result.meta.pagination.total
};
});
}, {
limit: 50
});
}
// trigger a background load of members plus labels for filter dropdown
setupController(controller) {
super.setupController(...arguments);
controller.fetchMembersTask.perform();
controller.fetchLabelsTask.perform();
}

View File

@ -2,7 +2,6 @@
<GhCanvasHeader class="gh-canvas-header members-header">
<h2 class="gh-canvas-title" data-test-screen-title>Members</h2>
<section class="view-actions">
{{!--
<GhMembersContentfilter
@selectedLabel={{this.selectedLabel}}
@availableLabels={{this.availableLabels}}
@ -14,11 +13,12 @@
{{svg-jar "search" class="gh-input-search-icon"}}
<GhTextInput
placeholder="Search members..."
@disabled={{true}}
@value={{this.searchText}}
@input={{action (mut this.searchText) value="target.value"}}
class="gh-members-list-searchfield {{if this.searchText "active"}}" />
</div>
--}}
<span class="dropdown">
<GhDropdownButton @dropdownName="members-actions-menu"
@classNames="gh-btn gh-btn-white gh-btn-icon only-has-icon gh-actions-cog" @title="Members Actions"
@ -46,7 +46,7 @@
</section>
</GhCanvasHeader>
{{#if this.members}}
{{#unless this.members.loading}}
<section class="view-container">
{{!--
{{#if this.filteredMembers}}
@ -89,7 +89,7 @@
<div class="gh-content">
<GhLoadingSpinner />
</div>
{{/if}}
{{/unless}}
</section>
{{outlet}}