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({ 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); } } } });