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:
parent
f36ebb9a24
commit
ad7c8ed423
|
@ -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}}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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}}
|
||||
|
|
Loading…
Reference in New Issue