WIP Details pane + contributor profiles
Adds a 3-pane layout option for showing details, as well as a dashboard sub-route for showing contributor details in the new details pane.
This commit is contained in:
@@ -1,20 +1,18 @@
|
|||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
|
|
||||||
|
router: service(),
|
||||||
|
|
||||||
tagName: 'table',
|
tagName: 'table',
|
||||||
classNames: 'contributor-list',
|
classNames: 'contributor-list',
|
||||||
selectedContributor: null,
|
selectedContributor: null,
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
toggleContributorInfo(contributor) {
|
openContributorDetails(contributor) {
|
||||||
if (contributor.showMetadata) {
|
this.router.transitionTo('dashboard.contributors.show', contributor);
|
||||||
contributor.set('showMetadata', false);
|
|
||||||
} else {
|
|
||||||
this.contributorList.setEach('showMetadata', false);
|
|
||||||
contributor.set('showMetadata', true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{#each contributorList as |c|}}
|
{{#each contributorList as |c|}}
|
||||||
<tr role="button" class={{if (is-current-user c.contributor) "current-user"}} {{action "toggleContributorInfo" c}}>
|
<tr role="button" class={{if (is-current-user c.contributor) "current-user"}} {{action "openContributorDetails" c.contributor}}>
|
||||||
<td class="person">
|
<td class="person">
|
||||||
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
|
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
|
||||||
</td>
|
</td>
|
||||||
@@ -15,25 +15,5 @@
|
|||||||
<span class="symbol">₭S</span>
|
<span class="symbol">₭S</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="metadata {{if (is-current-user c.contributor) "current-user"}} {{if c.showMetadata "visible"}}">
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://rinkeby.etherscan.io/address/{{c.contributor.account}}" target="_blank" rel="noopener">Inspect Ethereum transactions</a>
|
|
||||||
</li>
|
|
||||||
{{#if c.contributor.ipfsHash}}
|
|
||||||
<li>
|
|
||||||
<a href="https://ipfs.io/ipfs/{{c.contributor.ipfsHash}}" target="_blank" rel="noopener">Inspect IPFS profile</a>
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
<li>
|
|
||||||
{{link-to "Edit profile" "contributors.edit" c.contributor.id}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{{#if c.showMetadata}}
|
|
||||||
<pre>{{c.contributor.ipfsData}}</pre>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import { alias } from '@ember/object/computed';
|
import { alias } from '@ember/object/computed';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
|
||||||
|
const SIZES = {
|
||||||
|
'small': '128', // pixels
|
||||||
|
'medium': '256',
|
||||||
|
'large': '512'
|
||||||
|
}
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
contributor: null,
|
contributor: null,
|
||||||
tagName: 'img',
|
tagName: 'img',
|
||||||
classNames: ['avatar'],
|
classNames: ['avatar'],
|
||||||
|
classNameBindings: ['size'],
|
||||||
attributeBindings: ['src', 'title'],
|
attributeBindings: ['src', 'title'],
|
||||||
src: alias('contributor.avatarURL'),
|
size: 'small',
|
||||||
title: alias('contributor.name')
|
|
||||||
|
src: alias('avatarURL'),
|
||||||
|
title: alias('contributor.name'),
|
||||||
|
|
||||||
|
avatarURL: computed('contributor.github_uid', 'size', function() {
|
||||||
|
const github_uid = this.contributor.github_uid;
|
||||||
|
|
||||||
|
if (github_uid) {
|
||||||
|
return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=${SIZES[this.size]}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
|
||||||
|
export default Controller.extend({
|
||||||
|
|
||||||
|
roleName: computed('model.isCore', 'totalKreditsEarned', function() {
|
||||||
|
if (this.model.isCore) return 'Core Contributor';
|
||||||
|
if (this.model.totalKreditsEarned <= 5000) return 'Newcomer';
|
||||||
|
return 'Contributor';
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { computed } from '@ember/object';
|
|
||||||
import EmberObject from '@ember/object';
|
import EmberObject from '@ember/object';
|
||||||
import bignumber from 'kredits-web/utils/cps/bignumber';
|
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||||
import kreditsValue from 'kredits-web/utils/cps/kredits';
|
import kreditsValue from 'kredits-web/utils/cps/kredits';
|
||||||
@@ -20,12 +19,6 @@ export default EmberObject.extend({
|
|||||||
github_username: null,
|
github_username: null,
|
||||||
github_uid: null,
|
github_uid: null,
|
||||||
wiki_username: null,
|
wiki_username: null,
|
||||||
ipfsData: '',
|
ipfsData: ''
|
||||||
|
|
||||||
avatarURL: computed('github_uid', function() {
|
|
||||||
let github_uid = this.github_uid;
|
|
||||||
if (github_uid) {
|
|
||||||
return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=128`;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ const Router = EmberRouter.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
Router.map(function() {
|
Router.map(function() {
|
||||||
|
this.route('dashboard', function() {
|
||||||
|
this.route('contributors', function() {
|
||||||
|
this.route('show', { path: ':id' });
|
||||||
|
});
|
||||||
|
});
|
||||||
this.route('proposals', function() {
|
this.route('proposals', function() {
|
||||||
this.route('new');
|
this.route('new');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
import { alias } from '@ember/object/computed';
|
||||||
|
|
||||||
|
export default Route.extend({
|
||||||
|
|
||||||
|
kredits: service(),
|
||||||
|
contributors: alias('kredits.contributors'),
|
||||||
|
|
||||||
|
model(params) {
|
||||||
|
return this.contributors.findBy('id', params.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -5,7 +5,7 @@ main {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&#index {
|
&#dashboard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-row-gap: 2rem;
|
grid-row-gap: 2rem;
|
||||||
@@ -32,12 +32,19 @@ main {
|
|||||||
|
|
||||||
@media (min-width: 550px) {
|
@media (min-width: 550px) {
|
||||||
main {
|
main {
|
||||||
&#index {
|
&#dashboard {
|
||||||
grid-column-gap: 4rem;
|
grid-column-gap: 4rem;
|
||||||
grid-row-gap: 2rem;
|
grid-row-gap: 2rem;
|
||||||
grid-template-columns: 2fr 4fr;
|
grid-template-columns: 2fr 4fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"stats contributions";
|
"stats contributions";
|
||||||
|
|
||||||
|
&.with-details {
|
||||||
|
grid-column-gap: 3rem;
|
||||||
|
grid-template-columns: 2fr 4fr 2fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"stats contributions details";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -97,9 +97,11 @@ section {
|
|||||||
|
|
||||||
@import "buttons";
|
@import "buttons";
|
||||||
@import "forms";
|
@import "forms";
|
||||||
@import "components/topbar";
|
|
||||||
@import "components/loading-spinner";
|
|
||||||
@import "components/contributor-list";
|
|
||||||
@import "components/proposal-list";
|
|
||||||
@import "components/contribution-list";
|
@import "components/contribution-list";
|
||||||
|
@import "components/contributor-list";
|
||||||
|
@import "components/contributor-profile";
|
||||||
|
@import "components/loading-spinner";
|
||||||
|
@import "components/proposal-list";
|
||||||
|
@import "components/topbar";
|
||||||
@import "components/user-avatar";
|
@import "components/user-avatar";
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
section#contributor-profile {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
header {
|
||||||
|
z-index: 2;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin: 0 auto;
|
||||||
|
border: 3px solid rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
margin: -7.2rem 0 1.5rem;
|
||||||
|
padding: 6rem 1.2rem 2rem;
|
||||||
|
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
.button {
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,4 +4,18 @@ img.avatar {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 0.2rem;
|
margin-right: 0.2rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
margin: 0;
|
||||||
|
width: 10rem;
|
||||||
|
height: 10rem;
|
||||||
|
border-radius: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
margin: 0;
|
||||||
|
width: 256px;
|
||||||
|
height: 256px;
|
||||||
|
border-radius: 128px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<main id="index">
|
<main id="dashboard" class="with-details">
|
||||||
|
|
||||||
<div id="stats">
|
<div id="stats">
|
||||||
<section id="people">
|
<section id="people">
|
||||||
@@ -72,4 +72,8 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="details">
|
||||||
|
{{outlet}}
|
||||||
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<section id="contributor-profile">
|
||||||
|
<header>
|
||||||
|
{{user-avatar contributor=model size="medium"}}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h2>{{model.name}}</h2>
|
||||||
|
<p class="role">{{roleName}}</p>
|
||||||
|
|
||||||
|
{{#if model.url}}
|
||||||
|
<a href={{model.url}} target="_blank" rel="noopener">Web</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<p>
|
||||||
|
<a href="https://rinkeby.etherscan.io/address/{{model.account}}" class="button small" target="_blank" rel="noopener">Inspect Ethereum transactions</a>
|
||||||
|
{{#if model.ipfsHash}}
|
||||||
|
<a href="https://ipfs.io/ipfs/{{model.ipfsHash}}" class="button small" target="_blank" rel="noopener">Inspect IPFS profile</a>
|
||||||
|
{{/if}}
|
||||||
|
{{link-to "Edit profile" "contributors.edit" model class="button small"}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
Vendored
+5
-1
@@ -1,4 +1,5 @@
|
|||||||
import Model from 'kredits-web/models/contribution';
|
import Model from 'kredits-web/models/contribution';
|
||||||
|
import contributors from '../../tests/fixtures/contributors';
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
|
|
||||||
@@ -14,6 +15,9 @@ const data = [
|
|||||||
{ id: 9, contributorId: 3, confirmedAtBlock: 2000, claimed: false, vetoed: true, amount: 1500, kind: 'docs' },
|
{ id: 9, contributorId: 3, confirmedAtBlock: 2000, claimed: false, vetoed: true, amount: 1500, kind: 'docs' },
|
||||||
];
|
];
|
||||||
|
|
||||||
data.forEach(attrs => items.push(Model.create(attrs)));
|
data.forEach(attrs => {
|
||||||
|
attrs.contributor = contributors.findBy('id', attrs.contributorId.toString());
|
||||||
|
items.push(Model.create(attrs))
|
||||||
|
});
|
||||||
|
|
||||||
export default items;
|
export default items;
|
||||||
|
|||||||
Vendored
+3
-3
@@ -3,9 +3,9 @@ import Contributor from 'kredits-web/models/contributor';
|
|||||||
const contributors = [];
|
const contributors = [];
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{ id: 1, name: 'Bumi', totalKreditsEarned: 11500 },
|
{ id: 1, name: 'Bumi', totalKreditsEarned: 11500, github_uid: 318 },
|
||||||
{ id: 2, name: 'Râu Cao', totalKreditsEarned: 3000 },
|
{ id: 2, name: 'Râu Cao', totalKreditsEarned: 3000, github_uid: 842 },
|
||||||
{ id: 3, name: 'Manuel', totalKreditsEarned: 0 }
|
{ id: 3, name: 'Manuel', totalKreditsEarned: 0, github_uid: 54812 }
|
||||||
];
|
];
|
||||||
|
|
||||||
data.forEach(attrs => contributors.push(Contributor.create(attrs)));
|
data.forEach(attrs => contributors.push(Contributor.create(attrs)));
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ module('Integration | Component | contributor list', function(hooks) {
|
|||||||
|
|
||||||
const toplist = [
|
const toplist = [
|
||||||
{
|
{
|
||||||
contributor: Contributor.create({ id: 1, name: 'Bumi' }),
|
contributor: Contributor.create({ id: 1, name: 'Bumi', github_uid: 318 }),
|
||||||
amountConfirmed: 5500,
|
amountConfirmed: 5500,
|
||||||
amountUnconfirmed: 1000,
|
amountUnconfirmed: 1000,
|
||||||
amountTotal: 6500
|
amountTotal: 6500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
contributor: Contributor.create({ id: 2, name: 'Râu Cao' }),
|
contributor: Contributor.create({ id: 2, name: 'Râu Cao', github_uid: 842 }),
|
||||||
amountConfirmed: 1500,
|
amountConfirmed: 1500,
|
||||||
amountUnconfirmed: 2000,
|
amountUnconfirmed: 2000,
|
||||||
amountTotal: 3500
|
amountTotal: 3500
|
||||||
@@ -28,8 +28,8 @@ module('Integration | Component | contributor list', function(hooks) {
|
|||||||
|
|
||||||
assert.dom('tr:nth-child(1) td.person').hasText('Bumi');
|
assert.dom('tr:nth-child(1) td.person').hasText('Bumi');
|
||||||
assert.dom('tr:nth-child(1) span.amount').hasText('6500');
|
assert.dom('tr:nth-child(1) span.amount').hasText('6500');
|
||||||
assert.dom('tr:nth-child(3) td.person').hasText('Râu Cao');
|
assert.dom('tr:nth-child(2) td.person').hasText('Râu Cao');
|
||||||
assert.dom('tr:nth-child(3) span.amount').hasText('3500');
|
assert.dom('tr:nth-child(2) span.amount').hasText('3500');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it renders confirmed kredits earned', async function(assert) {
|
test('it renders confirmed kredits earned', async function(assert) {
|
||||||
@@ -38,7 +38,7 @@ module('Integration | Component | contributor list', function(hooks) {
|
|||||||
|
|
||||||
assert.dom('tr:nth-child(1) td.person').hasText('Bumi');
|
assert.dom('tr:nth-child(1) td.person').hasText('Bumi');
|
||||||
assert.dom('tr:nth-child(1) span.amount').hasText('5500');
|
assert.dom('tr:nth-child(1) span.amount').hasText('5500');
|
||||||
assert.dom('tr:nth-child(3) td.person').hasText('Râu Cao');
|
assert.dom('tr:nth-child(2) td.person').hasText('Râu Cao');
|
||||||
assert.dom('tr:nth-child(3) span.amount').hasText('1500');
|
assert.dom('tr:nth-child(2) span.amount').hasText('1500');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,39 @@ import { module, test } from 'qunit';
|
|||||||
import { setupRenderingTest } from 'ember-qunit';
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render } from '@ember/test-helpers';
|
import { render } from '@ember/test-helpers';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
import contributors from '../../../fixtures/contributors';
|
||||||
|
|
||||||
module('Integration | Component | user-avatar', function(hooks) {
|
module('Integration | Component | user-avatar', function(hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
test('it renders', async function(assert) {
|
test('it renders', async function(assert) {
|
||||||
await render(hbs`{{user-avatar}}`);
|
this.set('bumi', contributors.findBy('id', '1'));
|
||||||
|
await render(hbs`{{user-avatar contributor=bumi}}`);
|
||||||
|
|
||||||
assert.equal(this.element.textContent.trim(), '');
|
assert.equal(this.element.textContent.trim(), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('default image source URL', async function(assert) {
|
||||||
|
this.set('bumi', contributors.findBy('id', '1'));
|
||||||
|
await render(hbs`{{user-avatar contributor=bumi}}`);
|
||||||
|
|
||||||
|
assert.equal(this.element.querySelector('img').src,
|
||||||
|
`https://avatars2.githubusercontent.com/u/318?v=3&s=128`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('size-specific image source URLs', async function(assert) {
|
||||||
|
this.set('bumi', contributors.findBy('id', '1'));
|
||||||
|
this.set('size', 'medium');
|
||||||
|
|
||||||
|
await render(hbs`{{user-avatar contributor=bumi size=size}}`);
|
||||||
|
|
||||||
|
assert.equal(this.element.querySelector('img').src,
|
||||||
|
`https://avatars2.githubusercontent.com/u/318?v=3&s=256`);
|
||||||
|
|
||||||
|
this.set('size', 'large');
|
||||||
|
|
||||||
|
assert.equal(this.element.querySelector('img').src,
|
||||||
|
`https://avatars2.githubusercontent.com/u/318?v=3&s=512`);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { module, test } from 'qunit';
|
// import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
// import { setupTest } from 'ember-qunit';
|
||||||
import Contributor from 'kredits-web/models/contributor';
|
// import Contributor from 'kredits-web/models/contributor';
|
||||||
|
//
|
||||||
module('Unit | Model | contributor', function(hooks) {
|
// module('Unit | Model | contributor', function(hooks) {
|
||||||
setupTest(hooks);
|
// setupTest(hooks);
|
||||||
|
// });
|
||||||
test('#avatarURL() returns correct URL', function(assert) {
|
|
||||||
let model = Contributor.create();
|
|
||||||
model.set('github_uid', '318');
|
|
||||||
|
|
||||||
assert.equal(model.get('avatarURL'), 'https://avatars2.githubusercontent.com/u/318?v=3&s=128');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user