mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
🐛 Fixed infinite scroll on the team screen (#1095)
closes https://github.com/TryGhost/Ghost/issues/10307 - removed infinite scroll from the team screen - adjusted team screen behaviour to pull from the local cache and update in the background to speed up navigation to the screen - use `{{vertical-collection}}` to render the users list for faster initial render with many users
This commit is contained in:
parent
5e3de1c333
commit
434e8ba2f7
4 changed files with 77 additions and 78 deletions
|
@ -1,18 +1,17 @@
|
||||||
/* eslint-disable ghost/ember/alias-model-in-controller */
|
/* eslint-disable ghost/ember/alias-model-in-controller */
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
|
import RSVP from 'rsvp';
|
||||||
|
import {alias, sort} from '@ember/object/computed';
|
||||||
import {computed} from '@ember/object';
|
import {computed} from '@ember/object';
|
||||||
import {inject as service} from '@ember/service';
|
import {inject as service} from '@ember/service';
|
||||||
import {sort} from '@ember/object/computed';
|
import {task} from 'ember-concurrency';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
session: service(),
|
session: service(),
|
||||||
|
store: service(),
|
||||||
|
|
||||||
showInviteUserModal: false,
|
showInviteUserModal: false,
|
||||||
|
|
||||||
activeUsers: null,
|
|
||||||
suspendedUsers: null,
|
|
||||||
invites: null,
|
|
||||||
|
|
||||||
inviteOrder: null,
|
inviteOrder: null,
|
||||||
userOrder: null,
|
userOrder: null,
|
||||||
|
|
||||||
|
@ -22,17 +21,67 @@ export default Controller.extend({
|
||||||
this.userOrder = ['name', 'email'];
|
this.userOrder = ['name', 'email'];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
currentUser: alias('model'),
|
||||||
|
|
||||||
sortedInvites: sort('filteredInvites', 'inviteOrder'),
|
sortedInvites: sort('filteredInvites', 'inviteOrder'),
|
||||||
sortedActiveUsers: sort('activeUsers', 'userOrder'),
|
sortedActiveUsers: sort('activeUsers', 'userOrder'),
|
||||||
sortedSuspendedUsers: sort('suspendedUsers', 'userOrder'),
|
sortedSuspendedUsers: sort('suspendedUsers', 'userOrder'),
|
||||||
|
|
||||||
|
invites: computed(function () {
|
||||||
|
return this.store.peekAll('invite');
|
||||||
|
}),
|
||||||
|
|
||||||
filteredInvites: computed('invites.@each.isNew', function () {
|
filteredInvites: computed('invites.@each.isNew', function () {
|
||||||
return this.get('invites').filterBy('isNew', false);
|
return this.invites.filterBy('isNew', false);
|
||||||
|
}),
|
||||||
|
|
||||||
|
allUsers: computed(function () {
|
||||||
|
return this.store.peekAll('user');
|
||||||
|
}),
|
||||||
|
|
||||||
|
activeUsers: computed('allUsers.@each.status', function () {
|
||||||
|
return this.allUsers.filter((user) => {
|
||||||
|
return user.status !== 'inactive';
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
suspendedUsers: computed('allUsers.@each.status', function () {
|
||||||
|
return this.allUsers.filter((user) => {
|
||||||
|
return user.status === 'inactive';
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleInviteUserModal() {
|
toggleInviteUserModal() {
|
||||||
this.toggleProperty('showInviteUserModal');
|
this.toggleProperty('showInviteUserModal');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
backgroundUpdate: task(function* () {
|
||||||
|
let users = this.fetchUsers.perform();
|
||||||
|
let invites = this.fetchInvites.perform();
|
||||||
|
let roles = this.fetchRoles.perform();
|
||||||
|
|
||||||
|
try {
|
||||||
|
yield RSVP.all([users, invites, roles]);
|
||||||
|
} catch (error) {
|
||||||
|
this.send('error', error);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
fetchUsers: task(function* () {
|
||||||
|
yield this.store.query('user', {limit: 'all'});
|
||||||
|
}),
|
||||||
|
|
||||||
|
fetchInvites: task(function* () {
|
||||||
|
if (this.currentUser.isAuthorOrContributor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return yield this.store.query('invite', {limit: 'all'});
|
||||||
|
}),
|
||||||
|
|
||||||
|
fetchRoles: task(function* () {
|
||||||
|
return yield this.store.findAll('role');
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,58 +1,27 @@
|
||||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||||
import CurrentUserSettings from 'ghost-admin/mixins/current-user-settings';
|
import CurrentUserSettings from 'ghost-admin/mixins/current-user-settings';
|
||||||
import RSVP from 'rsvp';
|
|
||||||
import styleBody from 'ghost-admin/mixins/style-body';
|
import styleBody from 'ghost-admin/mixins/style-body';
|
||||||
import {inject as service} from '@ember/service';
|
import {inject as service} from '@ember/service';
|
||||||
|
|
||||||
export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
||||||
infinity: service(),
|
infinity: service(),
|
||||||
|
session: service(),
|
||||||
|
|
||||||
titleToken: 'Team',
|
titleToken: 'Team',
|
||||||
classNames: ['view-team'],
|
classNames: ['view-team'],
|
||||||
|
|
||||||
modelPath: 'controller.activeUsers',
|
|
||||||
perPage: 15,
|
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return this.get('session.user').then((user) => {
|
return this.session.user;
|
||||||
let modelPath = this.get('modelPath');
|
|
||||||
let perPage = this.get('perPage');
|
|
||||||
|
|
||||||
let modelPromises = {
|
|
||||||
activeUsers: this.infinity.model('user', {
|
|
||||||
modelPath,
|
|
||||||
perPage,
|
|
||||||
filter: 'status:-inactive',
|
|
||||||
startingPage: 1,
|
|
||||||
perPageParam: 'limit',
|
|
||||||
totalPagesParam: 'meta.pagination.pages'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// authors do not have permission to hit the invites or suspended users endpoint
|
|
||||||
if (!user.get('isAuthorOrContributor')) {
|
|
||||||
modelPromises.invites = this.store.query('invite', {limit: 'all'})
|
|
||||||
.then(() => this.store.peekAll('invite'));
|
|
||||||
|
|
||||||
// fetch suspended users separately so that infinite scroll still works
|
|
||||||
modelPromises.suspendedUsers = this.store.query('user', {limit: 'all', filter: 'status:inactive'});
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to load the roles into ember cache
|
|
||||||
// invites return role_id only and we do not offer a /role/:id endpoint
|
|
||||||
modelPromises.roles = this.get('store').query('role', {});
|
|
||||||
|
|
||||||
return RSVP.hash(modelPromises);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setupController(controller, models) {
|
setupController(controller) {
|
||||||
controller.setProperties(models);
|
this._super(...arguments);
|
||||||
|
controller.backgroundUpdate.perform();
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
reload() {
|
reload() {
|
||||||
this.refresh();
|
this.controller.backgroundUpdate.perform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<section class="gh-canvas">
|
|
||||||
<header class="gh-canvas-header">
|
|
||||||
<h2 class="gh-canvas-title" data-test-screen-title>Team members</h2>
|
|
||||||
{{!-- Do not show Invite user button to authors --}}
|
|
||||||
{{#unless session.user.isAuthorOrContributor}}
|
|
||||||
<section class="view-actions">
|
|
||||||
<button class="gh-btn gh-btn-green" {{action "toggleInviteUserModal"}} ><span>Invite People</span></button>
|
|
||||||
</section>
|
|
||||||
{{/unless}}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="gh-content">
|
|
||||||
{{gh-loading-spinner}}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
|
@ -2,7 +2,7 @@
|
||||||
<header class="gh-canvas-header">
|
<header class="gh-canvas-header">
|
||||||
<h2 class="gh-canvas-title" data-test-screen-title>Team members</h2>
|
<h2 class="gh-canvas-title" data-test-screen-title>Team members</h2>
|
||||||
{{!-- Do not show Invite user button to authors --}}
|
{{!-- Do not show Invite user button to authors --}}
|
||||||
{{#unless session.user.isAuthorOrContributor}}
|
{{#unless currentUser.isAuthorOrContributor}}
|
||||||
<section class="view-actions">
|
<section class="view-actions">
|
||||||
<button class="gh-btn gh-btn-green" {{action "toggleInviteUserModal"}} ><span>Invite People</span></button>
|
<button class="gh-btn gh-btn-green" {{action "toggleInviteUserModal"}} ><span>Invite People</span></button>
|
||||||
</section>
|
</section>
|
||||||
|
@ -16,9 +16,8 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<section class="gh-team">
|
<section class="gh-team">
|
||||||
|
|
||||||
{{!-- Show invited users to everyone except authors --}}
|
{{!-- Show invited users to everyone except authors --}}
|
||||||
{{#unless session.user.isAuthorOrContributor}}
|
{{#unless currentUser.isAuthorOrContributor}}
|
||||||
{{#if invites}}
|
{{#if invites}}
|
||||||
<section class="apps-grid-container gh-invited-users" data-test-invited-users>
|
<section class="apps-grid-container gh-invited-users" data-test-invited-users>
|
||||||
<span class="apps-grid-title">Invited users</span>
|
<span class="apps-grid-title">Invited users</span>
|
||||||
|
@ -75,32 +74,29 @@
|
||||||
<section class="apps-grid-container gh-active-users" data-test-active-users>
|
<section class="apps-grid-container gh-active-users" data-test-active-users>
|
||||||
<span class="apps-grid-title">Active users</span>
|
<span class="apps-grid-title">Active users</span>
|
||||||
<div class="apps-grid">
|
<div class="apps-grid">
|
||||||
{{!-- For authors/contributors only show their own user --}}
|
{{!-- For authors/contributors, only show their own user --}}
|
||||||
{{#if session.user.isAuthorOrContributor}}
|
{{#if currentUser.isAuthorOrContributor}}
|
||||||
{{#with session.user as |user|}}
|
{{#gh-user-active user=currentUser as |component|}}
|
||||||
{{#gh-user-active user=user as |component|}}
|
{{gh-user-list-item user=currentUser component=component}}
|
||||||
{{gh-user-list-item user=user component=component}}
|
{{/gh-user-active}}
|
||||||
{{/gh-user-active}}
|
|
||||||
{{/with}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#each sortedActiveUsers key="id" as |user|}}
|
{{#vertical-collection sortedActiveUsers
|
||||||
|
key="id"
|
||||||
|
containerSelector=".gh-main"
|
||||||
|
estimateHeight=75
|
||||||
|
as |user|
|
||||||
|
}}
|
||||||
{{#gh-user-active user=user as |component|}}
|
{{#gh-user-active user=user as |component|}}
|
||||||
{{gh-user-list-item user=user component=component}}
|
{{gh-user-list-item user=user component=component}}
|
||||||
{{/gh-user-active}}
|
{{/gh-user-active}}
|
||||||
{{/each}}
|
{{/vertical-collection}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{gh-infinity-loader
|
|
||||||
infinityModel=activeUsers
|
|
||||||
scrollable=".gh-main"
|
|
||||||
triggerOffset=500}}
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{!-- Don't show if we have no suspended users or logged in as an author --}}
|
{{!-- Don't show if we have no suspended users or logged in as an author --}}
|
||||||
{{#if (and suspendedUsers (not session.user.isAuthorOrContributor))}}
|
{{#if (and suspendedUsers (not currentUser.isAuthorOrContributor))}}
|
||||||
<section class="apps-grid-container gh-active-users" data-test-suspended-users>
|
<section class="apps-grid-container gh-active-users" data-test-suspended-users>
|
||||||
<span class="apps-grid-title">Suspended users</span>
|
<span class="apps-grid-title">Suspended users</span>
|
||||||
<div class="apps-grid">
|
<div class="apps-grid">
|
||||||
|
|
Loading…
Add table
Reference in a new issue