f7ee864071
Causing serious performance issues without the stable key
153 lines
5.1 KiB
JavaScript
153 lines
5.1 KiB
JavaScript
import Component from '@ember/component';
|
|
import EmberObject, { computed, observer } from '@ember/object';
|
|
import { sort } from '@ember/object/computed';
|
|
import { isEmpty, isPresent } from '@ember/utils';
|
|
import { inject as service } from '@ember/service';
|
|
|
|
export default Component.extend({
|
|
|
|
router: service(),
|
|
|
|
tagName: 'div',
|
|
classNames: ['contributions'],
|
|
|
|
selectedContribution: null,
|
|
expandedGroup: null,
|
|
|
|
showQuickFilter: false,
|
|
hideSmallContributions: false,
|
|
contributorId: null,
|
|
contributionKind: null,
|
|
|
|
kredits: service(),
|
|
|
|
contributorsSorting: Object.freeze(['name:asc']),
|
|
contributors: sort('kredits.contributors', 'contributorsSorting'),
|
|
|
|
contributorsActive: computed('contributors.[]', 'contributions', function() {
|
|
const activeIds = new Set(this.contributions.mapBy('contributorId'));
|
|
|
|
return this.contributors.filter(c => activeIds.has(c.id));
|
|
}),
|
|
|
|
contributionKinds: computed('contributions.[]', function() {
|
|
return this.contributions.mapBy('kind').uniq();
|
|
}),
|
|
|
|
// Groups the contributions by their `groupId` (derived from `url`; see
|
|
// utils/contribution-grouping-key), then filters the groups: a group is
|
|
// included when any of its items matches the active filters, and when
|
|
// included all of its items render (grouped rows stay whole — filtering by
|
|
// a contributor does not collapse a group into a singleton). Preserves the
|
|
// input order so the controller's date-sort is retained. Manual
|
|
// contributions without a URL each form their own single-item group.
|
|
//
|
|
// Each group object exposes: groupId, items, contributors, contributorIds,
|
|
// amount (single contribution's, not a sum), kind, description, url, date,
|
|
// isGrouped, visibleContributors (capped at 3), extraContributorCount.
|
|
groupedContributions: computed('contributions.[]', 'hideSmallContributions', 'contributorId', 'contributionKind', function() {
|
|
const groupsMap = new Map();
|
|
const orderedGroups = [];
|
|
|
|
this.contributions.forEach(c => {
|
|
const groupId = c.groupId;
|
|
if (isEmpty(groupId)) {
|
|
orderedGroups.push({ groupId: null, items: [c] });
|
|
} else if (groupsMap.has(groupId)) {
|
|
groupsMap.get(groupId).items.push(c);
|
|
} else {
|
|
const group = { groupId, items: [c] };
|
|
groupsMap.set(groupId, group);
|
|
orderedGroups.push(group);
|
|
}
|
|
});
|
|
|
|
return orderedGroups
|
|
.filter(raw => raw.items.any(c => this.matchesFilters(c)))
|
|
.map(raw => {
|
|
const items = raw.items;
|
|
const first = items.firstObject;
|
|
const contributorIds = items.mapBy('contributorId').uniq();
|
|
const contributors = contributorIds
|
|
.map(id => this.contributors.findBy('id', id))
|
|
.filter(Boolean);
|
|
|
|
return EmberObject.create({
|
|
key: raw.groupId || `c-${first.id}`,
|
|
groupId: raw.groupId,
|
|
items,
|
|
contributors,
|
|
contributorIds,
|
|
amount: first ? first.amount : null,
|
|
kind: first ? first.kind : null,
|
|
description: first ? first.description : null,
|
|
url: first ? first.url : null,
|
|
date: first ? first.date : null,
|
|
isGrouped: items.length > 1,
|
|
visibleContributors: contributors.slice(0, 3),
|
|
extraContributorCount: Math.max(0, contributors.length - 3)
|
|
});
|
|
});
|
|
}),
|
|
|
|
// Shared filter predicates for a single contribution. Used to decide group
|
|
// inclusion in `groupedContributions`. A group is shown when any of its
|
|
// items passes; "hide small contributions" thus keeps a group as long as
|
|
// any item is above the threshold.
|
|
matchesFilters (c) {
|
|
if (this.hideSmallContributions && c.amount <= 500) return false;
|
|
if (isPresent(this.contributorId) &&
|
|
c.contributorId !== parseInt(this.contributorId)) return false;
|
|
if (isPresent(this.contributionKind) &&
|
|
c.kind !== this.contributionKind) return false;
|
|
return true;
|
|
},
|
|
|
|
// Auto-expands the group containing the selected contribution so the
|
|
// selected child row is visible with the `selected` highlight.
|
|
autoExpandSelected: observer('selectedContributionId', function() {
|
|
this._autoExpandSelected();
|
|
}),
|
|
|
|
_autoExpandSelected () {
|
|
const selectedId = this.selectedContributionId;
|
|
if (!selectedId) return;
|
|
const group = this.groupedContributions.find(g =>
|
|
g.items.findBy('id', selectedId)
|
|
);
|
|
if (group && group.isGrouped && this.expandedGroup !== group.groupId) {
|
|
this.set('expandedGroup', group.groupId);
|
|
}
|
|
},
|
|
|
|
init () {
|
|
this._super(...arguments);
|
|
this._autoExpandSelected();
|
|
},
|
|
|
|
actions: {
|
|
|
|
veto (contributionId) {
|
|
if (this.contractInteractionEnabled) {
|
|
this.vetoContribution(contributionId);
|
|
} else {
|
|
window.alert('Only members can veto contributions. Please ask someone to set you up.');
|
|
}
|
|
},
|
|
|
|
openContributionDetails(contribution) {
|
|
this.router.transitionTo('dashboard.contributions.show', contribution);
|
|
},
|
|
|
|
toggleGroup (group) {
|
|
if (this.expandedGroup === group.groupId) {
|
|
this.set('expandedGroup', null);
|
|
} else {
|
|
this.set('expandedGroup', group.groupId);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
});
|