Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
970b390509 | ||
|
|
b8f4040def | ||
|
|
8fbecd928b |
No files matched your search
+1
-1
@@ -5,5 +5,5 @@
|
||||
|
||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
||||
*/
|
||||
"disableAnalytics": true
|
||||
"disableAnalytics": false
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# unconventional js
|
||||
/blueprints/*/files/
|
||||
/vendor/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
/release/
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
|
||||
# misc
|
||||
/coverage/
|
||||
!.*
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -1,47 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2017,
|
||||
sourceType: 'module'
|
||||
},
|
||||
globals: {
|
||||
console: true
|
||||
},
|
||||
plugins: [
|
||||
'ember'
|
||||
],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:ember/recommended'
|
||||
],
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
rules: {
|
||||
'ember/avoid-leaking-state-in-ember-objects': 'warn',
|
||||
'no-console': 'off'
|
||||
},
|
||||
overrides: [
|
||||
// node files
|
||||
{
|
||||
files: [
|
||||
'.eslintrc.js',
|
||||
'.template-lintrc.js',
|
||||
'ember-cli-build.js',
|
||||
'testem.js',
|
||||
'blueprints/*/index.js',
|
||||
'config/**/*.js',
|
||||
'lib/*/index.js',
|
||||
'server/**/*.js'
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
ecmaVersion: 2015
|
||||
},
|
||||
env: {
|
||||
browser: false,
|
||||
node: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
+7
-16
@@ -1,26 +1,17 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
/dist
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
/node_modules
|
||||
/bower_components
|
||||
|
||||
# misc
|
||||
/.env*
|
||||
/.pnp*
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/
|
||||
/coverage/*
|
||||
/libpeerconnection.log
|
||||
/npm-debug.log*
|
||||
/testem.log
|
||||
/yarn-error.log
|
||||
.tm_properties
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
npm-debug.log*
|
||||
testem.log
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"predef": [
|
||||
"document",
|
||||
"window",
|
||||
"-Promise"
|
||||
],
|
||||
"browser": true,
|
||||
"boss": true,
|
||||
"curly": true,
|
||||
"debug": false,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false,
|
||||
"eqnull": true,
|
||||
"esversion": 6,
|
||||
"unused": true
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: 'recommended',
|
||||
|
||||
rules: {
|
||||
'simple-unless': false
|
||||
}
|
||||
};
|
||||
+10
-17
@@ -1,32 +1,25 @@
|
||||
---
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
- "6"
|
||||
|
||||
sudo: false
|
||||
dist: xenial
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: always
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.npm
|
||||
|
||||
env:
|
||||
global:
|
||||
# See https://git.io/vdao3 for details.
|
||||
- JOBS=1
|
||||
- $HOME/.cache # includes bowers cache
|
||||
|
||||
before_install:
|
||||
- npm config set spin false
|
||||
- npm install -g bower
|
||||
- bower --version
|
||||
- npm install phantomjs-prebuilt
|
||||
- node_modules/phantomjs-prebuilt/bin/phantomjs --version
|
||||
|
||||
install:
|
||||
- npm install
|
||||
- bower install
|
||||
|
||||
script:
|
||||
- npm run lint:hbs
|
||||
- npm run lint:js
|
||||
- npm test
|
||||
@@ -10,24 +10,22 @@ A short introduction of this app could easily go here.
|
||||
You will need the following things properly installed on your computer.
|
||||
|
||||
* [Git](https://git-scm.com/)
|
||||
* [Node.js](https://nodejs.org/) (with npm)
|
||||
* [Node.js](https://nodejs.org/) (with NPM)
|
||||
* [Bower](https://bower.io/)
|
||||
* [Ember CLI](https://ember-cli.com/)
|
||||
* [Google Chrome](https://google.com/chrome/)
|
||||
* [PhantomJS](http://phantomjs.org/)
|
||||
|
||||
## Installation
|
||||
|
||||
* `git clone git@github.com:67P/kredits-web.git` this repository
|
||||
* `git clone <repository-url>` this repository
|
||||
* `cd kredits-web`
|
||||
* `npm install`
|
||||
* `bower install`
|
||||
|
||||
## Running / Development
|
||||
|
||||
|
||||
* `ember serve` - by default kredits-web connects to the Ethreum Kovan network
|
||||
* `ember serve`
|
||||
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
||||
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
|
||||
|
||||
See [working with locally deployed contracts](https://github.com/67P/kredits-web#working-with-locally-deployed-contracts) for details on how to develop with locally deployed contracts.
|
||||
|
||||
### Code Generators
|
||||
|
||||
@@ -38,12 +36,6 @@ Make use of the many generators for code, try `ember help generate` for more det
|
||||
* `ember test`
|
||||
* `ember test --server`
|
||||
|
||||
### Linting
|
||||
|
||||
* `npm run lint:hbs`
|
||||
* `npm run lint:js`
|
||||
* `npm run lint:js -- --fix`
|
||||
|
||||
### Building
|
||||
|
||||
* `ember build` (development)
|
||||
@@ -51,66 +43,35 @@ Make use of the many generators for code, try `ember help generate` for more det
|
||||
|
||||
### Deploying
|
||||
|
||||
_(You need collaborator permissions on the 5apps Deploy project.)_
|
||||
Specify what it takes to deploy your app.
|
||||
|
||||
`npm run deploy`
|
||||
|
||||
## Working with locally deployed contracts
|
||||
|
||||
The smart contracts and their JavaScript wrapper library are developed in the
|
||||
[kredits-contracts](https://github.com/67P/kredits-contracts) repo/package.
|
||||
For development you might want to run your own development chain with your own development contracts.
|
||||
The kredits-contracts helper scripts are available in kredits-web. See [kredits-contracts](https://github.com/67P/kredits-contracts) for more information.
|
||||
|
||||
You can run `kredits-web` on your machine, against a local, simulated Ethereum
|
||||
network, provided e.g. by [ganache](http://truffleframework.com/ganache/).
|
||||
Start your parity node with a KreditsChain:
|
||||
|
||||
npm run kredits-parity
|
||||
|
||||
[kredits-contracts](https://github.com/67P/kredits-contracts) holds all the tools
|
||||
to start and set up such a simulated network, as well as to deploy smart
|
||||
contracts to it.
|
||||
Deploy the contracts to your local chain:
|
||||
|
||||
These are the basic steps to get up and running:
|
||||
npm run kredits-deploy
|
||||
|
||||
#### 1. IPFS
|
||||
Run kredits-web with your localy deployed contracts:
|
||||
|
||||
Run a local IPFS deamon in offline mode.
|
||||
KREDITS_CONTRACT_ADDR=`kredits-inspect -c Kredits -r -w address` \
|
||||
TOKEN_CONTRACT_ADDR=`kredits-inspect -c Token -r -w address` \
|
||||
npm start
|
||||
|
||||
* Make sure CORS headers are configured. See [IPFS](#ipfs) for more info.
|
||||
* `ipfs daemon --offline`
|
||||
You need to have `./node_modules/.bin` in your path for this to work. Otherwise simply copy the contract addresses and set the environment variables manually.
|
||||
|
||||
|
||||
#### 2. kredits-contracts
|
||||
|
||||
Get your local Ethereum development node running. (See [kredits-contracts README](https://github.com/67P/kredits-contracts)
|
||||
for details.
|
||||
|
||||
* Clone [kredits-contracts](https://github.com/67P/kredits-contracts)
|
||||
* `npm install`
|
||||
* `npm run devchain` - runs a local development chain
|
||||
* `npm run bootstrap` - bootstrap runs deploys all the contracts
|
||||
* `npm link` - make the `kredits-contracts` module linkable as `kredits-contracts` on your machine (currently broken :( )
|
||||
|
||||
#### 3. kredits-web
|
||||
|
||||
With IPFS and Ethereum/ganache running, you can now start this Ember app.
|
||||
|
||||
* `npm link kredits-contracts` - link the local `kredits-contracts` package
|
||||
* `npm run start:local` - WEB3_PROVIDER_URL=http://localhost:7545 must be set for local settings
|
||||
|
||||
If you restart the devchain your network ID changes. So kredits-contracts does not find the correct DAO Kernel address.
|
||||
If that is the case the DAO address must be provided as `KREDITS_KERNEL_ADDRESS` environment variable.
|
||||
|
||||
* `KREDITS_KERNEL_ADDRESS=<0xYOURADDRESS> npm run start:local` - find your address with the `npm run dao:address` command in the kredits-contracts repo.
|
||||
|
||||
#### IPFS
|
||||
|
||||
Install IPFS with your favorite package manager and run
|
||||
|
||||
ipfs init (on initial installation)
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["localhost:4200"]'
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'
|
||||
|
||||
## Further Reading / Useful Links
|
||||
|
||||
* [ember.js](https://emberjs.com/)
|
||||
* [ember.js](http://emberjs.com/)
|
||||
* [ember-cli](https://ember-cli.com/)
|
||||
* Development Browser Extensions
|
||||
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||
|
||||
+6
-2
@@ -1,9 +1,13 @@
|
||||
import Application from '@ember/application';
|
||||
import Ember from 'ember';
|
||||
import Resolver from './resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from './config/environment';
|
||||
|
||||
const App = Application.extend({
|
||||
let App;
|
||||
|
||||
Ember.MODEL_FACTORY_INJECTIONS = true;
|
||||
|
||||
App = Ember.Application.extend({
|
||||
modulePrefix: config.modulePrefix,
|
||||
podModulePrefix: config.podModulePrefix,
|
||||
Resolver
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
attributes: null,
|
||||
contributors: Object.freeze([]),
|
||||
|
||||
isValidContributor: notEmpty('contributorId'),
|
||||
isValidKind: notEmpty('kind'),
|
||||
isValidAmount: computed('amount', function() {
|
||||
return parseInt(this.amount, 10) > 0;
|
||||
}),
|
||||
isValidDescription: notEmpty('description'),
|
||||
isValidUrl: notEmpty('url'),
|
||||
isValid: and('isValidContributor',
|
||||
'isValidKind',
|
||||
'isValidAmount',
|
||||
'isValidDescription'),
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('defaultDate', new Date());
|
||||
|
||||
// Default attributes used by reset
|
||||
this.set('attributes', {
|
||||
contributorId: null,
|
||||
kind: null,
|
||||
date: [new Date()],
|
||||
amount: null,
|
||||
description: null,
|
||||
url: null,
|
||||
});
|
||||
|
||||
this.reset();
|
||||
},
|
||||
|
||||
reset () {
|
||||
this.setProperties(this.attributes);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
submit () {
|
||||
if (!this.isValid) {
|
||||
alert('Invalid data. Please review and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = this.getProperties(Object.keys(this.attributes));
|
||||
const [ date/* , time */ ] = attributes.date[0].toISOString().split('T');
|
||||
attributes.date = date;
|
||||
|
||||
this.set('inProgress', true);
|
||||
|
||||
this.save(attributes)
|
||||
.then((/*contribution*/) => {
|
||||
this.reset();
|
||||
}, err => {
|
||||
console.warn(err);
|
||||
window.alert('Something went wrong. Check the browser console for details.');
|
||||
})
|
||||
.finally(() => this.set('inProgress', false));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
<form {{action "submit" on="submit"}}>
|
||||
<p>
|
||||
<select required onchange={{action (mut contributorId) value="target.value"}}>
|
||||
<option value="" selected disabled hidden>Contributor</option>
|
||||
{{#each contributors as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut kind) value="target.value"}}>
|
||||
<option value="" selected disabled hidden>Kind</option>
|
||||
<option value="community" selected={{eq kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option>
|
||||
<option value="special" selected={{eq kind "special"}}>Special</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
{{ember-flatpickr
|
||||
allowInput=false
|
||||
altFormat="F j, Y"
|
||||
altInput=true
|
||||
altInputClass="date-alt"
|
||||
date=date
|
||||
dateFormat="Y-m-d"
|
||||
defaultDate=defaultDate
|
||||
maxDate=defaultDate
|
||||
onChange=(action (mut date))
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="500"
|
||||
value=amount
|
||||
class=(if isValidAmount "valid" "")}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="Description"
|
||||
value=description
|
||||
class=(if isValidDescription "valid" "")}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="URL (optional)"
|
||||
value=url
|
||||
class=(if isValidUrl "valid" "")}}
|
||||
</p>
|
||||
<p class="actions">
|
||||
{{input type="submit"
|
||||
disabled=inProgress
|
||||
value=(if inProgress "Processing" "Save")}}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -1,76 +1,79 @@
|
||||
import Component from '@ember/component';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isPresent } from '@ember/utils';
|
||||
import Ember from 'ember';
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
|
||||
const {
|
||||
Component,
|
||||
isPresent,
|
||||
inject: {
|
||||
service
|
||||
},
|
||||
computed
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
attributes: null,
|
||||
newContributor: null,
|
||||
inProgress: false,
|
||||
|
||||
// TODO: add proper address validation
|
||||
isValidAccount: notEmpty('account'),
|
||||
isValidName: notEmpty('name'),
|
||||
isValidURL: notEmpty('url'),
|
||||
isValidGithubUID: notEmpty('github_uid'),
|
||||
isValidGithubUsername: notEmpty('github_username'),
|
||||
isValidGiteaUsername: notEmpty('gitea_username'),
|
||||
isValidWikiUsername: notEmpty('wiki_username'),
|
||||
isValid: and(
|
||||
'isValidAccount',
|
||||
isValidAddress: function() {
|
||||
return this.get('kredits.web3')
|
||||
.isAddress(this.get('newContributor.address'));
|
||||
}.property('kredits.web3', 'newContributor.address'),
|
||||
|
||||
isValidName: function() {
|
||||
return isPresent(this.get('newContributor.name'));
|
||||
}.property('newContributor.name'),
|
||||
|
||||
isValidURL: function() {
|
||||
return isPresent(this.get('newContributor.url'));
|
||||
}.property('newContributor.url'),
|
||||
|
||||
isValidGithubUID: function() {
|
||||
return isPresent(this.get('newContributor.github_uid'));
|
||||
}.property('newContributor.github_uid'),
|
||||
|
||||
isValidGithubUsername: function() {
|
||||
return isPresent(this.get('newContributor.github_username'));
|
||||
}.property('newContributor.github_username'),
|
||||
|
||||
isValidWikiUsername: function() {
|
||||
return isPresent(this.get('newContributor.wiki_username'));
|
||||
}.property('newContributor.wiki_username'),
|
||||
|
||||
isValid: computed.and(
|
||||
'isValidAddress',
|
||||
'isValidName',
|
||||
'isValidGithubUID'
|
||||
),
|
||||
|
||||
inProgress: false,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.setDefaultAttributes();
|
||||
this.reset();
|
||||
},
|
||||
|
||||
setDefaultAttributes () {
|
||||
if (isPresent(this.attributes)) { return; }
|
||||
|
||||
this.set('attributes', {
|
||||
account: null,
|
||||
name: null,
|
||||
kind: 'person',
|
||||
url: null,
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
gitea_username: null,
|
||||
wiki_username: null
|
||||
});
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.setProperties(this.attributes);
|
||||
this.setProperties({
|
||||
newContributor: Contributor.create({ kind: 'person' }),
|
||||
inProgress: false
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
submit () {
|
||||
if (!this.isValid) {
|
||||
alert('Invalid data. Please review and try again.');
|
||||
save() {
|
||||
if (!this.get('contractInteractionEnabled')) {
|
||||
alert('Only core team members can add new contributors. Please ask someone to set you up.');
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = Object.keys(this.attributes);
|
||||
const contributor = this.getProperties(attributes);
|
||||
if (this.get('isValid')) {
|
||||
this.set('inProgress', true);
|
||||
|
||||
this.set('inProgress', true);
|
||||
|
||||
this.save(contributor).then(() => {
|
||||
this.reset();
|
||||
}).catch(err => {
|
||||
console.warn(err);
|
||||
window.alert('Something went wrong. Please check the browser console.');
|
||||
}).finally(() => {
|
||||
this.set('inProgress', false);
|
||||
});
|
||||
this.get('kredits').addContributor(this.get('newContributor')).then(contributor => {
|
||||
this.reset();
|
||||
this.get('contributors').pushObject(contributor);
|
||||
window.scroll(0,0);
|
||||
});
|
||||
} else {
|
||||
alert('Invalid data. Please review and try again.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,50 +1,59 @@
|
||||
<form {{action "submit" on="submit"}}>
|
||||
<form {{action "save" on="submit"}}>
|
||||
<p>
|
||||
<label for="c-account">Ethereum account</label>
|
||||
{{input name="account" id="c-account"
|
||||
type="text" value=account
|
||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||
class=(if isValidAccount "valid" "")}}
|
||||
{{input type="checkbox" name="is-core" id="is-core" checked=newContributor.isCore}}
|
||||
<label for="is-core" class="checkbox">
|
||||
Core team member (can add contributors)
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-kind">Kind</label>
|
||||
<select required onchange={{action (mut kind) value="target.value"}} id="c-kind">
|
||||
<option value="person" selected={{eq kind "person"}}>Person</option>
|
||||
<option value="organization" selected={{eq kind "organization"}}>Organization</option>
|
||||
{{input name="address"
|
||||
type="text"
|
||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||
value=newContributor.address
|
||||
class=(if isValidAddress 'valid' '')}}
|
||||
</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut newContributor.kind) value="target.value"}}>
|
||||
<option value="person" selected={{eq newContributor.kind "person"}}>Person</option>
|
||||
<option value="organization" selected={{eq newContributor.kind "organization"}}>Organization</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-name">Name</label>
|
||||
{{input name="name" type="text" value=name placeholder="Zero Cool"
|
||||
class=(if isValidName "valid" "") id="c-name"}}
|
||||
{{input name="name"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value=newContributor.name
|
||||
class=(if isValidName 'valid' '')}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-url">URL</label>
|
||||
{{input name="url" type="text" value=url placeholder="http://zerocool.bit"
|
||||
class=(if isValidURL "valid" "") id="c-url"}}
|
||||
{{input name="url"
|
||||
type="text"
|
||||
placeholder="URL"
|
||||
value=newContributor.url
|
||||
class=(if isValidURL 'valid' '')}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-github-uid">GitHub UID</label>
|
||||
{{input name="github_uid" type="text" value=github_uid placeholder="2342"
|
||||
class=(if isValidGithubUID "valid" "") id="c-github-uid"}}
|
||||
{{input name="github_uid"
|
||||
type="text"
|
||||
placeholder="GitHub UID (123)"
|
||||
value=newContributor.github_uid
|
||||
class=(if isValidGithubUID 'valid' '')}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-github-username">GitHub username</label>
|
||||
{{input name="github_username" type="text" value=github_username placeholder="zerocool"
|
||||
class=(if isValidGithubUsername "valid" "") id="c-github-username"}}
|
||||
{{input name="github_username"
|
||||
type="text"
|
||||
placeholder="GitHub username"
|
||||
value=newContributor.github_username
|
||||
class=(if isValidGithubUsername 'valid' '')}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-gitea-username">Gitea username</label>
|
||||
{{input name="gitea_username" type="text" value=gitea_username placeholder="zerocool"
|
||||
class=(if isValidGiteaUsername "valid" "") id="c-gitea-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-wiki-username">Wiki username</label>
|
||||
{{input name="wiki_username" type="text" value=wiki_username placeholder="ZeroCool"
|
||||
class=(if isValidWikiUsername "valid" "") id="c-wiki-username"}}
|
||||
{{input name="wiki_username"
|
||||
type="text"
|
||||
placeholder="Wiki Username"
|
||||
value=newContributor.wiki_username
|
||||
class=(if isValidWikiUsername 'valid' '')}}
|
||||
</p>
|
||||
<p class="actions">
|
||||
{{input type="submit" disabled=inProgress
|
||||
value=(if inProgress "Processing" "Save")}}
|
||||
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
|
||||
</p>
|
||||
</form>
|
||||
@@ -1,63 +1,65 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import Ember from 'ember';
|
||||
|
||||
const {
|
||||
Component,
|
||||
isPresent,
|
||||
inject: {
|
||||
service
|
||||
},
|
||||
computed
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
attributes: null,
|
||||
contributors: Object.freeze([]),
|
||||
kredits: service(),
|
||||
|
||||
isValidContributor: notEmpty('contributorId'),
|
||||
isValidAmount: computed('amount', function() {
|
||||
return parseInt(this.amount, 10) > 0;
|
||||
proposal: null,
|
||||
contributors: null,
|
||||
inProgress: false,
|
||||
|
||||
isValidRecipient: computed('proposal.recipientAddress', function() {
|
||||
return this.get('kredits.web3').isAddress(this.get('proposal.recipientAddress'));
|
||||
}),
|
||||
isValidDescription: notEmpty('description'),
|
||||
isValidUrl: notEmpty('url'),
|
||||
isValid: and('isValidContributor',
|
||||
'isValidAmount',
|
||||
'isValidDescription'),
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
isValidAmount: computed('proposal.amount', function() {
|
||||
return parseInt(this.get('proposal.amount'), 10) > 0;
|
||||
}),
|
||||
|
||||
// Default attributes used by reset
|
||||
this.set('attributes', {
|
||||
contributorId: null,
|
||||
kind: 'community',
|
||||
amount: null,
|
||||
description: null,
|
||||
url: null,
|
||||
});
|
||||
},
|
||||
isValidUrl: computed('proposal.url', function() {
|
||||
return isPresent(this.get('proposal.url'));
|
||||
}),
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.reset();
|
||||
},
|
||||
isValidDescription: computed('proposal.description', function() {
|
||||
return isPresent(this.get('proposal.description'));
|
||||
}),
|
||||
|
||||
reset: function() {
|
||||
this.setProperties(this.attributes);
|
||||
},
|
||||
isValid: computed.and('isValidRecipient',
|
||||
'isValidAmount',
|
||||
'isValidDescription'),
|
||||
|
||||
actions: {
|
||||
submit() {
|
||||
if (!this.isValid) {
|
||||
save() {
|
||||
if (! this.get('isValid')) {
|
||||
alert('Invalid data. Please review and try again.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.set('inProgress', true);
|
||||
let proposal = this.get('proposal');
|
||||
|
||||
let attributes = Object.keys(this.attributes);
|
||||
let proposal = this.getProperties(attributes);
|
||||
let saved = this.save(proposal);
|
||||
// Set the recipient's IPFS profile hash so it can be used in the
|
||||
// contribution object (which is to be stored in IPFS as well)
|
||||
let contributor = this.get('contributors').findBy('address', proposal.get('recipientAddress'));
|
||||
proposal.set('recipientProfile', contributor.get('ipfsHash'));
|
||||
|
||||
// The promise handles inProgress
|
||||
this.set('inProgress', saved);
|
||||
|
||||
saved.then(() => {
|
||||
this.reset();
|
||||
window.scroll(0,0);
|
||||
window.alert('Proposal added.');
|
||||
});
|
||||
this.get('kredits').addProposal(proposal)
|
||||
.then(() => {
|
||||
this.attrs.onSave();
|
||||
}).catch((error) => {
|
||||
Ember.Logger.error('[add-proposal] error creating the proposal', error);
|
||||
alert('Something went wrong.');
|
||||
}).finally(() => {
|
||||
this.set('inProgress', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
<form {{action "submit" on="submit"}}>
|
||||
<form {{action "save" on="submit"}}>
|
||||
<p>
|
||||
<select required onchange={{action (mut contributorId) value="target.value"}}>
|
||||
<select required onchange={{action (mut proposal.recipientAddress) value="target.value"}}>
|
||||
<option value="" selected disabled hidden>Contributor</option>
|
||||
{{#each contributors as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.github_username}}</option>
|
||||
<option value={{contributor.address}} selected={{eq proposal.recipientAddress contributor.address}}>{{contributor.github_username}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut kind) value="target.value"}}>
|
||||
<option value="community" selected={{eq kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option>
|
||||
<select required onchange={{action (mut proposal.kind) value="target.value"}}>
|
||||
<option value="community" selected={{eq proposal.kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq proposal.kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq proposal.kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq proposal.kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq proposal.kind "ops"}}>IT Operations</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="100"
|
||||
value=amount
|
||||
class=(if isValidAmount "valid" "")}}
|
||||
value=proposal.amount
|
||||
class=(if isValidAmount 'valid' '')}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="Description"
|
||||
value=description
|
||||
class=(if isValidDescription "valid" "")}}
|
||||
value=proposal.description
|
||||
class=(if isValidDescription 'valid' '')}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="URL (optional)"
|
||||
value=url
|
||||
class=(if isValidUrl "valid" "")}}
|
||||
value=proposal.url
|
||||
class=(if isValidUrl 'valid' '')}}
|
||||
</p>
|
||||
<p class="actions">
|
||||
{{input type="submit"
|
||||
disabled=(is-pending inProgress)
|
||||
value=(if (is-pending inProgress) "Processing" "Save")}}
|
||||
{{#link-to "index"}}Back{{/link-to}}
|
||||
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
|
||||
{{#link-to 'index'}}Back{{/link-to}}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
let categoryColors = {
|
||||
community: "#fb6868",
|
||||
design: "#fbe468",
|
||||
dev: "#e068fb",
|
||||
docs: "#97fb68",
|
||||
ops: "#8f68fb",
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
contributions: null,
|
||||
chartOptions: Object.freeze({
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}),
|
||||
|
||||
chartData: computed('contributions', function() {
|
||||
let kredits = this.contributions
|
||||
.filterBy('vetoed', false)
|
||||
.map(c => {
|
||||
return { kind: c.kind, amount: c.amount }
|
||||
})
|
||||
.reduce(function (kinds, c) {
|
||||
if (c.kind in kinds) {
|
||||
kinds[c.kind] = kinds[c.kind] + c.amount
|
||||
} else {
|
||||
kinds[c.kind] = c.amount;
|
||||
}
|
||||
return kinds;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
datasets: [{
|
||||
data: [
|
||||
kredits['community'],
|
||||
kredits['design'],
|
||||
kredits['dev'],
|
||||
kredits['ops'],
|
||||
kredits['docs'],
|
||||
],
|
||||
borderColor: [
|
||||
categoryColors.community,
|
||||
categoryColors.design,
|
||||
categoryColors.dev,
|
||||
categoryColors.ops,
|
||||
categoryColors.docs,
|
||||
],
|
||||
borderWidth: 1
|
||||
}],
|
||||
labels: [
|
||||
'Community',
|
||||
'Design',
|
||||
'Development',
|
||||
'Operations & Infrastructure',
|
||||
'Documentation'
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="chart">
|
||||
{{ember-chart type="doughnut"
|
||||
data=chartData
|
||||
options=chartOptions
|
||||
width=200
|
||||
height=200}}
|
||||
</div>
|
||||
@@ -1,64 +1,16 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { sort } from '@ember/object/computed';
|
||||
import { isPresent } from '@ember/utils';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Component.extend({
|
||||
export default Ember.Component.extend({
|
||||
|
||||
tagName: 'div',
|
||||
classNames: ['contributions'],
|
||||
tagName: 'ul',
|
||||
classNames: ['contribution-list'],
|
||||
|
||||
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() {
|
||||
let activeIds = this.contributions.mapBy('contributorId')
|
||||
.map(id => id.toString())
|
||||
.uniq();
|
||||
return this.contributors.filter(c => {
|
||||
return activeIds.includes(c.id.toString());
|
||||
contributionsWithContributors: function() {
|
||||
return this.get('contributions').map((c) => {
|
||||
c.set('contributor', this.get('contributors')
|
||||
.findBy('address', c.get('recipientAddress')));
|
||||
return c;
|
||||
});
|
||||
}),
|
||||
|
||||
contributionKinds: computed('contributions.[]', function() {
|
||||
return this.contributions.mapBy('kind').uniq();
|
||||
}),
|
||||
|
||||
contributionsFiltered: computed('contributions.[]', 'hideSmallContributions', 'contributorId', 'contributionKind', function() {
|
||||
return this.contributions.filter(c => {
|
||||
let included = true;
|
||||
|
||||
if (this.hideSmallContributions &&
|
||||
c.amount <= 500) { included = false; }
|
||||
|
||||
if (isPresent(this.contributorId) &&
|
||||
c.contributorId.toString() !== this.contributorId.toString()) { included = false; }
|
||||
|
||||
if (isPresent(this.contributionKind) &&
|
||||
c.kind !== this.contributionKind) { included = false; }
|
||||
|
||||
return included;
|
||||
});
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
||||
veto (contributionId) {
|
||||
if (this.contractInteractionEnabled) {
|
||||
this.vetoContribution(contributionId);
|
||||
} else {
|
||||
window.alert('Only members can veto contributions. Please ask someone to set you up.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}.property('contributions.@each')
|
||||
|
||||
});
|
||||
@@ -1,58 +1,7 @@
|
||||
{{#if showQuickFilter}}
|
||||
<div class="quick-filter">
|
||||
<p>
|
||||
<label class="filter-contributor">
|
||||
Contributor:
|
||||
<select onchange={{action (mut contributorId) value="target.value"}}>
|
||||
<option value="" selected>all</option>
|
||||
{{#each contributorsActive as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="filter-contribution-kind">
|
||||
Kind:
|
||||
<select onchange={{action (mut contributionKind) value="target.value"}}>
|
||||
<option value="" selected>all</option>
|
||||
{{#each contributionKinds as |kind|}}
|
||||
<option value={{kind}} selected={{eq contributionKind kind}}>{{capitalize-string kind}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="filter-contribution-size">
|
||||
{{input type="checkbox" checked=hideSmallContributions}}
|
||||
Hide small contributions
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<ul class="contribution-list">
|
||||
{{#each contributionsFiltered as |contribution|}}
|
||||
<li data-contribution-id={{contribution.id}} class="{{contribution-status contribution}} {{if contribution.vetoed "vetoed"}}">
|
||||
<p class="meta">
|
||||
<span class="recipient">{{user-avatar contributor=contribution.contributor}}</span>
|
||||
<span class="category {{contribution.kind}}">({{contribution.kind}})</span>
|
||||
<span class="title">
|
||||
{{#if contribution.url}}
|
||||
<a href={{contribution.url}} target="_blank" rel="noopener" title={{contribution.description}}>{{contribution.description}}</a>
|
||||
{{else}}
|
||||
{{contribution.description}}
|
||||
{{/if}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="kredits-amount">
|
||||
<span class="amount">{{contribution.amount}}</span><span class="symbol">₭S</span>
|
||||
</p>
|
||||
{{#unless contribution.vetoed}}
|
||||
{{#unless (is-confirmed-contribution contribution)}}
|
||||
<p class="voting">
|
||||
<button {{action "veto" contribution.id}} class="small danger">veto</button>
|
||||
</p>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#each contributionsWithContributors as |contribution|}}
|
||||
<li title="({{contribution.kind}}) {{contribution.description}}">
|
||||
<span class="category {{contribution.kind}}">♥</span>
|
||||
<span class="amount">{{contribution.amount}}</span><span class="symbol">₭S</span>
|
||||
for <span class="recipient">{{contribution.contributor.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
@@ -1,9 +1,6 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
router: service(),
|
||||
export default Ember.Component.extend({
|
||||
|
||||
tagName: 'table',
|
||||
classNames: 'contributor-list',
|
||||
@@ -11,8 +8,13 @@ export default Component.extend({
|
||||
|
||||
actions: {
|
||||
|
||||
openContributorDetails(contributor) {
|
||||
this.router.transitionTo('dashboard.contributors.show', contributor);
|
||||
toggleContributorInfo(contributor) {
|
||||
if (contributor.get('showMetadata')) {
|
||||
contributor.set('showMetadata', false);
|
||||
} else {
|
||||
this.get('contributors').setEach('showMetadata', false);
|
||||
contributor.set('showMetadata', true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
<tbody>
|
||||
{{#each contributorList as |c|}}
|
||||
<tr role="button" class={{if (is-current-user c.contributor) "current-user"}} {{action "openContributorDetails" c.contributor}}>
|
||||
{{#each contributors as |contributor|}}
|
||||
<tr class="{{if contributor.isCurrentUser 'current-user'}}" {{action "toggleContributorInfo" contributor}}>
|
||||
<td class="person">
|
||||
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
|
||||
<img class="avatar" src={{contributor.avatarURL}}>
|
||||
{{contributor.name}}
|
||||
</td>
|
||||
<td class="kredits">
|
||||
<span class="amount">
|
||||
{{#if showUnconfirmedKredits}}
|
||||
{{c.amountTotal}}
|
||||
{{else}}
|
||||
{{c.amountConfirmed}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="amount">{{contributor.kredits}}</span>
|
||||
<span class="symbol">₭S</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="metadata {{if contributor.isCurrentUser 'current-user'}} {{if contributor.showMetadata 'visible'}}">
|
||||
<td colspan="2">
|
||||
<ul>
|
||||
<li><a href="https://testnet.etherscan.io/address/{{contributor.address}}">Inspect Ethereum transactions</a></li>
|
||||
{{#if contributor.ipfsHash}}
|
||||
<li><a href="https://ipfs.io/ipfs/{{contributor.ipfsHash}}">Inspect IPFS profile</a></li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
@@ -1,15 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
account: null,
|
||||
|
||||
iconComponentName: computed('account.site', function() {
|
||||
if (this.account.site.match(/github|gitea|wiki/)) {
|
||||
return 'icon-account-' + this.account.site.replace(/\./g, '-');
|
||||
} else {
|
||||
return 'icon-web-globe';
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
<a href={{account.url}} target="_blank" rel="noopener" title="{{account.username}} on {{account.site}}">
|
||||
<span class="site">{{account.site}}</span>
|
||||
{{component iconComponentName}}
|
||||
</a>
|
||||
@@ -1,5 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg class="icon-gitea" width="400" height="400" version="1.1" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g fill-rule="evenodd">
|
||||
<path d="m235.61 136.7 7e-3 32.7 4.493 2.189c2.471 1.204 4.628 2.25 4.793 2.324s0.3-15.626 0.3-34.889v-35.024h-9.6l7e-3 32.7m-22.336 30.528c-2.787 0.548-5.894 2.175-8.11 4.244-2.296 2.145-1.694 0.967-24.197 47.328l-11.164 23v4.6c0 4.305 0.076 4.75 1.185 6.942 2.815 5.56 1.055 4.606 69.415 37.629l4.2 2.029h4.221c10.104 0 11.144-1.319 26.676-33.8 0.999-2.09 4.15-8.57 7.001-14.4s6.916-14.2 9.033-18.6l3.849-8 0.01-4.219c0.02-8.49-2.644-11.721-14.326-17.371-4.215-2.038-11.264-5.461-15.664-7.606-15.241-7.429-22.802-11.07-23.125-11.137-0.418-0.086-1.177 1.299-5.112 9.333l-3.234 6.6 1.19 2.2c0.654 1.21 1.308 3.038 1.454 4.063l0.265 1.862 5.747 2.8c22.047 10.742 22.975 15.314 9.007 44.377l-3.317 6.902 1.162 2.098c3.865 6.974-0.923 15.098-8.898 15.098-12.557 0-13.717-18.335-1.278-20.2l1.842-0.276 3.101-6.462c12.037-25.079 11.953-25.952-3.232-33.53l-7.333-3.659-2.319 1.032c-1.276 0.567-3.151 1.153-4.167 1.301l-1.848 0.269-6.785 13.855-6.786 13.854 1.109 1.747c6.109 9.633-5.532 20.771-14.552 13.924-6.729-5.108-4.39-16.742 3.633-18.073 0.812-0.134 1.71-0.318 1.996-0.408 0.429-0.136 6.592-12.372 13.118-26.044l1.37-2.87-0.782-1.33c-3.586-6.099 0.331-14.253 7.353-15.306l1.683-0.252 3.969-8.045c2.183-4.425 4.019-8.168 4.08-8.319s-2.979-1.758-6.755-3.571c-8.092-3.885-10.4-4.452-14.685-3.609" />
|
||||
<path class="fg" d="m73.4 97.876c-2.903 0.376-8.068 1.377-8.8 1.705-0.44 0.197-2.915 1.007-5.5 1.8s-4.7 1.617-4.7 1.831c0 0.213-0.225 0.388-0.5 0.388-0.63 1e-3 -5.615 2.344-5.893 2.771-0.114 0.175-0.837 0.638-1.607 1.029-5.482 2.788-14.835 11.523-18.63 17.4-0.639 0.99-1.274 1.89-1.409 2-0.659 0.534-4.752 8.933-5.555 11.4-0.502 1.54-1.048 2.98-1.213 3.2-0.587 0.782-2.031 7.531-2.489 11.633-0.256 2.292-0.61 4.167-0.785 4.167-0.443 0-0.403 17.45 0.042 18.305 0.199 0.382 0.566 2.748 0.816 5.258 0.493 4.939 1.847 12.516 2.428 13.578 0.198 0.363 0.83 2.369 1.405 4.459 1.927 7.009 10.26 22.927 12.44 23.764 0.303 0.116 0.55 0.481 0.55 0.812 0 0.813 10.667 11.552 12.3 12.383 0.715 0.364 1.3 0.821 1.3 1.017 0 0.195 0.945 0.934 2.1 1.642s2.19 1.41 2.3 1.561 1.28 0.875 2.6 1.609c1.32 0.735 2.67 1.552 3 1.816s1.95 1.086 3.6 1.827c1.65 0.74 3.101 1.486 3.224 1.658 0.123 0.171 1.741 0.865 3.596 1.542s3.557 1.384 3.783 1.572c0.226 0.187 2.388 0.907 4.804 1.599s4.753 1.423 5.193 1.624c1.299 0.593 7.258 1.856 12.2 2.586 2.53 0.374 4.96 0.83 5.4 1.013 1.151 0.48 8.262 1.165 15.067 1.45l5.866 0.247 0.877 2.594c0.903 2.673 4.652 10.529 5.195 10.884 0.168 0.11 0.585 0.83 0.928 1.6 0.685 1.538 1.927 3.686 2.313 4 0.135 0.11 0.826 1.19 1.536 2.4 1.544 2.634 4.561 7.175 5.293 7.968 0.289 0.313 0.975 1.236 1.525 2.051 1.729 2.563 7.039 9.405 8.557 11.025 0.244 0.26 1.523 1.692 2.843 3.183 3.476 3.925 7.354 7.774 10.261 10.183 1.397 1.157 2.629 2.223 2.739 2.368s1.096 0.884 2.191 1.643c1.094 0.758 2.33 1.681 2.745 2.051s1.229 0.911 1.809 1.203 1.884 1.026 2.897 1.632c1.952 1.167 7.201 3.093 8.428 3.093 0.402 0 0.73 0.18 0.73 0.4 0 0.511 115.46 0.557 115.77 0.046 0.121-0.194 1.074-0.482 2.119-0.639 2.437-0.365 7.765-2.307 9.27-3.378 0.64-0.456 1.334-0.829 1.542-0.829 0.451 0 5.639-3.687 8.597-6.111 2.854-2.337 5.633-5.107 8.718-8.689 1.42-1.65 2.681-3.09 2.8-3.2 0.581-0.536 5.161-6.76 5.812-7.9 0.409-0.715 0.869-1.3 1.023-1.3 0.153 0 0.938-1.125 1.742-2.5 2.068-3.533 4.108-6.822 4.561-7.352 0.213-0.248 1.043-1.755 1.845-3.348s1.62-3.1 1.818-3.348c0.687-0.864 5.069-9.661 5.555-11.152 0.269-0.825 0.642-1.5 0.829-1.5s0.878-1.396 1.536-3.101c0.658-1.706 1.671-4.047 2.251-5.203 0.581-1.156 1.405-3.116 1.832-4.356 0.426-1.239 0.934-2.453 1.129-2.696 0.194-0.244 0.946-2.154 1.671-4.244s1.446-3.89 1.602-4c0.155-0.11 0.964-2.36 1.797-5 0.832-2.64 1.627-4.89 1.766-5s0.861-2.27 1.604-4.8c0.742-2.53 1.484-4.782 1.649-5.003 0.164-0.222 0.872-2.562 1.573-5.2 0.701-2.639 1.412-4.977 1.58-5.197s0.965-3.19 1.77-6.6 1.623-6.488 1.817-6.84c0.195-0.353 0.919-3.503 1.611-7 0.692-3.498 1.431-6.678 1.642-7.068 0.211-0.389 0.938-4.169 1.616-8.4 0.677-4.23 1.388-8.052 1.579-8.492 0.192-0.44 0.708-3.77 1.147-7.4 4.258-35.198 1.497-55.451-8.208-60.199l-2.219-1.086-7.473 0.266c-4.111 0.146-7.834 0.417-8.274 0.602-0.801 0.336-13.842 0.941-52 2.412-11.22 0.433-22.2 0.881-24.4 0.997-2.2 0.115-11.334 0.415-20.298 0.665l-16.298 0.455-0.204 69.922-9.392-4.634-8e-3 -65.272-19.3-0.277c-10.615-0.153-19.84-0.441-20.5-0.642-0.66-0.2-6.69-0.551-13.4-0.779-21.82-0.743-58.18-2.495-59-2.843-0.44-0.187-4.04-0.453-8-0.591s-11.34-0.508-16.4-0.822c-8.365-0.519-22.619-0.685-25.6-0.298m13.657 31.23c0.269 0.173 0.809 3.742 1.313 8.694 0.471 4.62 1.021 8.76 1.221 9.2 0.201 0.44 0.561 2.42 0.801 4.4 0.709 5.856 2.382 15.937 2.787 16.8 0.207 0.44 0.943 3.68 1.636 7.2s1.4 6.58 1.571 6.8 0.803 2.56 1.405 5.2c0.601 2.64 1.333 5.34 1.627 6 0.293 0.66 1.193 3.63 2 6.6 0.808 2.97 1.597 5.49 1.754 5.6s0.884 2.27 1.614 4.8c0.731 2.53 1.455 4.69 1.608 4.8 0.154 0.11 0.868 2.09 1.587 4.4 0.72 2.31 1.494 4.429 1.721 4.71 0.226 0.28 0.341 0.567 0.255 0.637-0.263 0.215-9.713-1.339-10.37-1.704-0.337-0.188-1.237-0.468-2-0.623-4-0.814-10.947-2.703-11.712-3.185-0.481-0.304-1.325-0.675-1.875-0.825-1.377-0.377-5.355-1.954-5.6-2.22-0.11-0.12-1.447-0.841-2.971-1.604-1.524-0.762-3.216-1.791-3.759-2.286s-1.16-0.9-1.372-0.9c-1.422 0-11.329-9.68-12.558-12.27-0.347-0.731-0.775-1.33-0.951-1.33-0.593 0-4.638-8.175-5.591-11.3-0.519-1.705-1.071-3.1-1.226-3.1s-0.372-0.495-0.482-1.1c-0.111-0.605-0.599-3.08-1.084-5.5-1.119-5.58-1.528-15.681-0.814-20.111 0.516-3.208 1.895-8.141 2.372-8.489 0.15-0.11 0.752-1.19 1.338-2.4 2.053-4.245 6.611-8.836 11.098-11.177 1.43-0.746 2.69-1.485 2.8-1.643 0.28-0.402 6.231-2.23 8.4-2.58 0.99-0.16 2.003-0.454 2.251-0.654 0.248-0.199 2.678-0.513 5.4-0.697s5.039-0.357 5.149-0.385c0.11-0.029 0.406 0.08 0.657 0.242m131.79 38.066c2.262 0.371 16.092 6.678 15.866 7.236-0.061 0.151-1.897 3.894-4.08 8.319l-3.969 8.045-1.683 0.252c-7.022 1.053-10.939 9.207-7.353 15.306l0.782 1.33-1.37 2.87c-6.526 13.672-12.689 25.908-13.118 26.044-0.286 0.09-1.184 0.274-1.996 0.408-8.023 1.331-10.362 12.965-3.633 18.073 9.02 6.847 20.661-4.291 14.552-13.924l-1.109-1.747 6.786-13.854 6.785-13.855 1.848-0.269c1.016-0.148 2.891-0.734 4.167-1.301l2.319-1.032 7.333 3.659c15.185 7.578 15.269 8.451 3.232 33.53l-3.101 6.462-1.842 0.276c-12.439 1.865-11.279 20.2 1.278 20.2 7.975 0 12.763-8.124 8.898-15.098l-1.162-2.098Line truncated
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,5 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
<svg id="svg825" class="icon-github" width="260" height="260" enable-background="new 0 0 212.4575 186.23" version="1.1" viewBox="0 0 260 260" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(23.766 36.885)">
|
||||
<path d="m151.79 86.234c-7.078 0.1229-14.154 0.5605-21.232 0.7069-12.318 0.248-24.64 0.5664-36.961 0.512-11.176-0.0472-22.358-0.9728-33.518-0.7052-7.3164 0.1776-14.373 2.2948-20.131 7.584-9.9472 9.1368-12.522 21-11.314 33.807 1.92 20.357 9.2716 31.449 29.115 37.064 15.793 4.4704 31.963 5.2088 48.199 4.9744 5.254 0 10.51 0.1367 15.758-0.0292 12.723-0.4064 25.264-2.2072 37.358-6.3596 10.846-3.7225 18.799-10.691 22.572-22.166 2.6736-8.1288 3.7148-16.388 2.5544-24.853-1.7772-12.959-13.275-30.86-32.4-30.535zm-81.576 59.996c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20zm76 0c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20z" fill="none"/>
|
||||
<path class="fg" d="m200.25 59.188c-2.5076-3.4493-4.3632-6.3888-3.8748-11.44 0.6796-7.0664-0.3536-14.396-1.3204-21.518-1.2188-8.9843-3.5236-17.74-7.1776-26.23-9.0332 1.9356-17.981 3.49-25.963 8.0976-8.4648 4.8908-16.695 10.215-25.08 15.25-0.9532 0.5704-2.3124 0.8848-3.3848 0.7032-17.967-3.1368-35.92-3.0196-53.898-0.086-1.3632 0.2244-3.0724-0.2345-4.2948-0.9608-4.4436-2.6604-8.6564-5.7344-13.123-8.3516-11.656-6.844-23.66-12.822-37.494-14.59-0.3928 0.8597-0.7188 1.4785-0.9612 2.1253-4.164 11.22-6.6424 22.844-7.4276 34.82-0.2872 4.3516-0.6092 8.8048 0 13.072 0.3456 2.4296-4e-3 3.8788-1.3924 5.6132-4.8516 6.0488-8.6368 12.717-11.102 20.219-4.6972 14.295-4.4004 28.861-2.2756 43.475 1.7268 11.974 4.7344 23.537 11.324 33.873 13.186 20.682 32.892 29.774 55.572 31.705 19.308 1.6464 38.8 1.1992 58.213 1.2364 13.855 0.0251 27.57-1.4924 40.715-6.6389 20.375-7.9727 33.859-22.604 40.275-44.166 3.7344-12.545 5.2324-25.396 4.8144-38.498-0.4412-13.81-4.0504-26.566-12.144-37.711zm-18.609 82.435c-3.7736 11.475-11.727 18.444-22.572 22.166-12.094 4.1524-24.635 5.9532-37.358 6.3596-5.248 0.1659-10.504 0.0292-15.758 0.0292-16.236 0.2344-32.406-0.504-48.199-4.9744-19.844-5.6152-27.195-16.707-29.115-37.064-1.2072-12.807 1.3672-24.67 11.314-33.807 5.7576-5.2892 12.814-7.4064 20.131-7.584 11.16-0.2676 22.342 0.658 33.518 0.7052 12.32 0.0544 24.642-0.264 36.961-0.512 7.0784-0.1464 14.154-0.584 21.232-0.7069 19.125-0.3243 30.623 17.576 32.4 30.535 1.1604 8.4647 0.1192 16.724-2.5544 24.853z"/>
|
||||
<ellipse class="fg" cx="70.214" cy="126.23" rx="16" ry="20"/>
|
||||
<ellipse class="fg" cx="146.21" cy="126.23" rx="16" ry="20"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,5 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
<svg class="icon-wiki" width="120" height="120" version="1.1" viewBox="0 0 120 120" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(18.1,3)">
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="6.2 5 45.7 20.5 45.7 91 6.2 75.5"/>
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="90 5 50.5 20.5 50.5 91 90 75.5"/>
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="87 88 87 81.8 60.4 91"/>
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="9.2 88 9.2 81.8 34.9 91"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 703 B |
@@ -1,5 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg class="icon-web-globe" width="60" height="60" enable-background="new 0 0 64 64" version="1.1" viewBox="0 0 60 60" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<path class="fg" d="m48.1 14.3c-0.4-0.5-0.9-1-1.4-1.5-4.3-4.2-10.2-6.8-16.7-6.8s-12.4 2.6-16.7 6.8c-0.5 0.5-1 1-1.4 1.5-3.7 4.2-5.9 9.7-5.9 15.7s2.2 11.5 5.8 15.7c0.4 0.5 0.9 1 1.4 1.5 4.3 4.2 10.3 6.8 16.8 6.8s12.5-2.6 16.8-6.9c0.5-0.5 0.9-0.9 1.4-1.5 3.6-4.1 5.8-9.6 5.8-15.6s-2.2-11.5-5.9-15.7zm3.9 14.7h-8.1c-0.1-3-1-7.3-2.4-10.5 1.8-0.8 3.5-1.8 5.1-3 3.1 3.6 5.1 8.5 5.4 13.5zm-21-20.1c3 2.3 5.8 5.2 7.6 8.5-2.4 0.9-4.6 1.3-7.6 1.4zm-2 9.9c-3-0.1-5.2-0.6-7.6-1.4 1.8-3.3 4.6-6.3 7.6-8.5zm0 2v8.2h-10.8c0.1-3 0.9-6.8 2.3-9.8 2.7 1 5.5 1.5 8.5 1.6zm0 10.2v8c-3 0.1-5.9 0.7-8.6 1.7-1.4-3-2.1-5.7-2.3-9.7zm0 10v10.1c-3-2.3-5.8-5.3-7.7-8.6 2.4-0.9 4.7-1.4 7.7-1.5zm2 0c3 0.1 5.3 0.6 7.7 1.5-1.9 3.4-4.7 6.3-7.7 8.6zm0-2v-8h10.8c-0.1 4-0.9 6.7-2.3 9.7-2.6-1-5.5-1.5-8.5-1.7zm0-10v-8.2c3-0.1 5.8-0.7 8.5-1.6 1.4 3 2.2 6.8 2.3 9.8zm9.5-12.3c-1.7-3.2-4.1-6-6.9-8.3 4.4 0.6 8.4 2.7 11.5 5.6-1.4 1.1-3 1.9-4.6 2.7zm-14.1-8.4c-2.9 2.3-5.2 5.2-6.9 8.3-1.6-0.7-3.2-1.6-4.6-2.6 3.1-2.9 7.1-5 11.5-5.7zm-12.9 7.2c1.6 1.2 3.3 2.2 5.1 3-1.5 3.3-2.3 7.5-2.4 10.5h-8.2c0.3-5 2.3-9.9 5.5-13.5zm-5.5 15.5h8.1c0.1 4 1 7.2 2.4 10.5-1.8 0.8-3.5 1.8-5.1 3-3.2-3.7-5.1-8.5-5.4-13.5zm11.4 12.2c1.7 3.2 4.1 6.1 7 8.4-4.5-0.7-8.5-2.8-11.6-5.8 1.5-1 3-1.8 4.6-2.6zm14.2 8.5c2.9-2.4 5.3-5.2 7-8.4 1.6 0.7 3.2 1.6 4.6 2.6-3.1 3-7.2 5-11.6 5.8zm13-7.3c-1.6-1.2-3.3-2.2-5.1-3 1.4-3.2 2.3-6.5 2.4-10.5h8.1c-0.3 5.1-2.2 9.9-5.4 13.5z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,6 +1,6 @@
|
||||
import Component from '@ember/component';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Component.extend({
|
||||
export default Ember.Component.extend({
|
||||
|
||||
classNames: ['loading-spinner']
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" x="0px" y="0px" viewBox="0 0 132 100"
|
||||
enable-background="new 0 0 100 100" xml:space="preserve">
|
||||
<path id="path-comet"
|
||||
<path id="path-comet"
|
||||
d="M79.062,24.173L79.062,24.173l-0.021-0.011c-0.068-0.03-0.137-0.062-0.204-0.093L42.392,7.337
|
||||
c0,0,3.065,14.104,4.458,18.019l0,0c0,0-41.754-12.481-46.85-13.67c1.67,6.52,30.607,62.492,30.607,62.492
|
||||
c5.848,11.873,19.394,18.485,33.522,18.485c19.811,0,35.87-16.059,35.87-35.869C100,42.313,91.418,29.837,79.062,24.173z">
|
||||
@@ -10,5 +10,5 @@
|
||||
</svg>
|
||||
</p>
|
||||
<p>
|
||||
Loading data...
|
||||
Loading data from Ethereum...
|
||||
</p>
|
||||
@@ -1,6 +1,6 @@
|
||||
import Component from '@ember/component';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Component.extend({
|
||||
export default Ember.Component.extend({
|
||||
|
||||
tagName: 'ul',
|
||||
classNames: ['proposal-list'],
|
||||
@@ -8,8 +8,8 @@ export default Component.extend({
|
||||
actions: {
|
||||
|
||||
confirm(proposalId) {
|
||||
if (this.contractInteractionEnabled) {
|
||||
this.confirmProposal(proposalId);
|
||||
if (this.get('contractInteractionEnabled')) {
|
||||
this.sendAction('confirmAction', proposalId);
|
||||
} else {
|
||||
window.alert('Only members can vote on proposals. Please ask someone to set you up.');
|
||||
}
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
{{#each proposals as |proposal|}}
|
||||
<li data-proposal-id={{proposal.id}} class={{if proposal.isExecuted "confirmed" "unconfirmed"}}>
|
||||
<p class="meta">
|
||||
<span class="category {{proposal.kind}}">♥ ({{proposal.kind}})</span>
|
||||
<span class="recipient">{{proposal.contributor.name}}:</span>
|
||||
</p>
|
||||
<p class="kredits-amount">
|
||||
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
|
||||
</p>
|
||||
<p class="description">
|
||||
<span class="description">{{proposal.description}}</span>
|
||||
</p>
|
||||
<p class="voting">
|
||||
{{#unless proposal.isExecuted}}
|
||||
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
|
||||
<button {{action "confirm" proposal.id}}>+1</button>
|
||||
{{/unless}}
|
||||
</p>
|
||||
<li data-proposal-id={{proposal.id}} title="({{proposal.kind}}) {{proposal.description}}">
|
||||
<span class="category {{proposal.kind}}">♥</span>
|
||||
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
|
||||
for <span class="recipient">{{proposal.recipientName}}</span>
|
||||
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
|
||||
|
||||
{{#unless proposal.executed}}<button {{action "confirm" proposal.id}}>+1</button>{{/unless}}
|
||||
</li>
|
||||
{{/each}}
|
||||
@@ -1,30 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
const SIZES = {
|
||||
'small': '128', // pixels
|
||||
'medium': '256',
|
||||
'large': '512'
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
contributor: null,
|
||||
tagName: 'img',
|
||||
classNames: ['avatar'],
|
||||
classNameBindings: ['size'],
|
||||
attributeBindings: ['src', 'title'],
|
||||
size: 'small',
|
||||
|
||||
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]}`;
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
Whitespace-only changes.
@@ -1,6 +1,4 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Controller.extend({
|
||||
kredits: service(),
|
||||
export default Ember.Controller.extend({
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { alias, filterBy, sort } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
contributors: alias('kredits.contributors'),
|
||||
minedContributors: filterBy('contributors', 'id'),
|
||||
|
||||
contributorsSorting: Object.freeze(['name:asc']),
|
||||
sortedContributors: sort('minedContributors', 'contributorsSorting'),
|
||||
|
||||
actions: {
|
||||
|
||||
save (contribution) {
|
||||
const contributor = this.contributors.findBy('id', contribution.contributorId);
|
||||
contribution.contributorIpfsHash = contributor.ipfsHash;
|
||||
|
||||
return this.kredits.addContribution(contribution)
|
||||
.then(contribution => {
|
||||
this.transitionToRoute('index');
|
||||
return contribution;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
actions: {
|
||||
|
||||
save (attributes) {
|
||||
return this.kredits
|
||||
.updateContributor(this.model.id, attributes)
|
||||
.then(() => this.transitionToRoute('index'))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
actions: {
|
||||
|
||||
save (contributor) {
|
||||
return this.kredits
|
||||
.addContributor(contributor)
|
||||
.then(() => this.transitionToRoute('index'))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, not, sort } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
kredits: service(),
|
||||
|
||||
showDetailsPane: false,
|
||||
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
contributions: alias('kredits.contributions'),
|
||||
contributionsConfirmed: alias('kredits.contributionsConfirmed'),
|
||||
contributionsUnconfirmed: alias('kredits.contributionsUnconfirmed'),
|
||||
|
||||
contributionsSorting: Object.freeze(['date:desc', 'time:desc', 'id:desc']),
|
||||
contributionsUnconfirmedSorted: sort('contributionsUnconfirmed', 'contributionsSorting'),
|
||||
contributionsConfirmedSorted: sort('contributionsConfirmed', 'contributionsSorting'),
|
||||
|
||||
kreditsByContributor: alias('kredits.kreditsByContributor'),
|
||||
|
||||
kreditsToplistSorting: computed('showUnconfirmedKredits', function(){
|
||||
return this.showUnconfirmedKredits ? ['amountTotal:desc'] : ['amountConfirmed:desc'];
|
||||
}),
|
||||
kreditsToplist: sort('kreditsByContributor', 'kreditsToplistSorting'),
|
||||
|
||||
showUnconfirmedKredits: true,
|
||||
hideUnconfirmedKredits: not('showUnconfirmedKredits'),
|
||||
|
||||
showQuickFilterUnconfirmed: false,
|
||||
showQuickFilterConfirmed: false,
|
||||
|
||||
actions: {
|
||||
|
||||
vetoContribution (contributionId) {
|
||||
this.kredits.veto(contributionId).then(transaction => {
|
||||
console.debug('[controllers:index] Veto submitted to Ethereum blockhain: '+transaction.hash);
|
||||
});
|
||||
},
|
||||
|
||||
confirmProposal (proposalId) {
|
||||
this.kredits.vote(proposalId).then(transaction => {
|
||||
console.debug('[controllers:index] Vote submitted to Ethereum blockhain: '+transaction.hash);
|
||||
});
|
||||
},
|
||||
|
||||
toggleQuickFilterUnconfirmed () {
|
||||
this.toggleProperty('showQuickFilterUnconfirmed');
|
||||
},
|
||||
|
||||
toggleQuickFilterConfirmed () {
|
||||
this.toggleProperty('showQuickFilterConfirmed');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
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';
|
||||
})
|
||||
|
||||
});
|
||||
@@ -0,0 +1,148 @@
|
||||
import Ember from 'ember';
|
||||
import Proposal from 'kredits-web/models/proposal';
|
||||
|
||||
const {
|
||||
computed,
|
||||
inject: {
|
||||
service
|
||||
}
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
contractInteractionEnabled: computed.alias('kredits.web3Provided'),
|
||||
|
||||
findContributorByAddress(address) {
|
||||
return this.get('model.contributors')
|
||||
.findBy('address', address);
|
||||
},
|
||||
|
||||
proposalsOpen: function() {
|
||||
let proposals = this.get('model.proposals')
|
||||
.filterBy('executed', false)
|
||||
.map(p => {
|
||||
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
|
||||
return p;
|
||||
});
|
||||
return proposals;
|
||||
}.property('model.proposals.[]', 'model.proposals.@each.executed', 'model.contributors.[]'),
|
||||
|
||||
proposalsClosed: function() {
|
||||
let proposals = this.get('model.proposals')
|
||||
.filterBy('executed', true)
|
||||
.map(p => {
|
||||
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
|
||||
return p;
|
||||
});
|
||||
return proposals;
|
||||
}.property('model.proposals.[]', 'model.proposals.@each.executed', 'model.contributors.[]'),
|
||||
|
||||
proposalsSorting: ['id:desc'],
|
||||
proposalsClosedSorted: Ember.computed.sort('proposalsClosed', 'proposalsSorting'),
|
||||
proposalsOpenSorted: Ember.computed.sort('proposalsOpen', 'proposalsSorting'),
|
||||
|
||||
contributorsWithKredits: function() {
|
||||
return this.get('model.contributors').filter(c => {
|
||||
return c.get('kredits') !== 0;
|
||||
});
|
||||
}.property('model.contributors.@each.kredits'),
|
||||
|
||||
contributorsSorting: ['kredits:desc'],
|
||||
contributorsSorted: Ember.computed.sort('contributorsWithKredits', 'contributorsSorting'),
|
||||
|
||||
watchContractEvents: function() {
|
||||
let events = this.get('kredits.kreditsContract')
|
||||
.allEvents(/* [additionalFilterObject], */);
|
||||
|
||||
events.watch((error, data) => {
|
||||
Ember.Logger.debug('[index] Received contract event', data);
|
||||
|
||||
switch (data.event) {
|
||||
case 'ProposalCreated':
|
||||
this._handleProposalCreated(data);
|
||||
break;
|
||||
case 'ProposalExecuted':
|
||||
this._handleProposalExecuted(data);
|
||||
break;
|
||||
case 'ProposalVoted':
|
||||
this._handleProposalVoted(data);
|
||||
break;
|
||||
case 'Transfer':
|
||||
this._handleTransfer(data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}.on('init'),
|
||||
|
||||
_handleProposalCreated(data) {
|
||||
if (Ember.isPresent(this.get('model.proposals')
|
||||
.findBy('id', data.args.id.toNumber()))) {
|
||||
Ember.Logger.debug('[index] proposal exists, not adding from event');
|
||||
return false;
|
||||
}
|
||||
|
||||
let proposal = Proposal.create({
|
||||
id: data.args.id.toNumber(),
|
||||
creatorAddress: data.args.creator,
|
||||
recipientAddress: data.args.recipient,
|
||||
recipientName: null,
|
||||
votesCount: 0,
|
||||
votesNeeded: 2,
|
||||
amount: data.args.amount.toNumber(),
|
||||
executed: false,
|
||||
url: data.args.url,
|
||||
ipfsHash: data.args.ipfsHash
|
||||
});
|
||||
|
||||
this.get('model.proposals').pushObject(proposal);
|
||||
},
|
||||
|
||||
_handleProposalExecuted(data) {
|
||||
if (this.get('model.proposals')
|
||||
.findBy('id', data.args.id.toNumber())
|
||||
.get('executed')) {
|
||||
Ember.Logger.debug('[index] proposal already executed, not adding from event');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.get('model.proposals')
|
||||
.findBy('id', data.args.id.toNumber())
|
||||
.setProperties({
|
||||
'executed': true,
|
||||
'votesCount': 2 // TODO use real count
|
||||
});
|
||||
|
||||
this.get('model.contributors')
|
||||
.findBy('address', data.args.recipient)
|
||||
.incrementProperty('kredits', data.args.amount.toNumber());
|
||||
},
|
||||
|
||||
_handleProposalVoted(data) {
|
||||
this.get('model.proposals')
|
||||
.findBy('id', data.args.id.toNumber())
|
||||
.incrementProperty('votesCount', 1);
|
||||
},
|
||||
|
||||
_handleTransfer(data) {
|
||||
this.get('model.contributors')
|
||||
.findBy('address', data.args.from)
|
||||
.incrementProperty('kredits', - data.args.value.toNumber());
|
||||
this.get('model.contributors')
|
||||
.findBy('address', data.args.to)
|
||||
.incrementProperty('kredits', data.args.value.toNumber());
|
||||
},
|
||||
|
||||
|
||||
actions: {
|
||||
|
||||
confirmProposal(proposalId) {
|
||||
this.get('kredits').vote(proposalId).then(transactionId => {
|
||||
window.confirm('Vote submitted to Ethereum blockhain: '+transactionId);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,24 +1,29 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { alias, filterBy } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Ember from 'ember';
|
||||
import QueryParams from 'ember-parachute';
|
||||
|
||||
export default Controller.extend({
|
||||
kredits: service(),
|
||||
|
||||
contributors: alias('kredits.contributors'),
|
||||
minedContributors: filterBy('contributors', 'id'),
|
||||
|
||||
actions: {
|
||||
save(proposal) {
|
||||
// contributorIpfsHash is needed for the proposal ipfs data. I'm not happy to do this here but I think to load all the contributors in addProposal again is a bit too much. I hope we can refactor it later.
|
||||
let contributor = this.contributors.findBy('id', proposal.contributorId);
|
||||
proposal.contributorIpfsHash = contributor.get('ipfsHash');
|
||||
|
||||
return this.kredits.addProposal(proposal)
|
||||
.then((proposal) => {
|
||||
this.transitionToRoute('index');
|
||||
return proposal;
|
||||
});
|
||||
}
|
||||
export const queryParams = new QueryParams({
|
||||
recipient: {
|
||||
defaultValue: ''
|
||||
},
|
||||
amount: {
|
||||
defaultValue: ''
|
||||
},
|
||||
url: {
|
||||
defaultValue: ''
|
||||
},
|
||||
ipfsHash: {
|
||||
defaultValue: ''
|
||||
}
|
||||
});
|
||||
|
||||
export default Ember.Controller.extend(queryParams.Mixin, {
|
||||
|
||||
contributors: null,
|
||||
|
||||
actions: {
|
||||
onSave() {
|
||||
this.transitionToRoute('index');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
|
||||
export default Helper.extend({
|
||||
compute([string]) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
kredits: service(),
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
compute([contribution]) {
|
||||
if (contribution.vetoed) {
|
||||
return 'vetoed';
|
||||
} else if (contribution.confirmedAt > this.currentBlock) {
|
||||
return 'unconfirmed';
|
||||
} else {
|
||||
return 'confirmed'
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
kredits: service(),
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
compute([contribution]) {
|
||||
return !contribution.vetoed &&
|
||||
(contribution.confirmedAt <= this.currentBlock);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { isPresent } from '@ember/utils';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
kredits: service(),
|
||||
currentUser: alias('kredits.currentUser'),
|
||||
|
||||
compute([contributor]) {
|
||||
return isPresent(contributor) && isPresent(this.currentUser) &&
|
||||
contributor.account === this.currentUser.account;
|
||||
}
|
||||
|
||||
});
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
|
||||
{{content-for "head"}}
|
||||
|
||||
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
|
||||
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
||||
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
|
||||
<link rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
||||
|
||||
{{content-for "head-footer"}}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* This pauses the app's boot process until the window load event is fired.
|
||||
* It allows the user provided Web3 instance (e.g. Metamask, Mist Browser)
|
||||
* to be inserted before we try to use it.
|
||||
*/
|
||||
export default {
|
||||
name: 'defer-loading',
|
||||
initialize: function(application) {
|
||||
// Load event already fired, so web3 should be loaded if available
|
||||
if (window.windowLoadComplete) { return true; }
|
||||
|
||||
// Pause app loading
|
||||
application.deferReadiness();
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
// Continue app loading
|
||||
application.advanceReadiness();
|
||||
});
|
||||
}
|
||||
};
|
||||
+42
-17
@@ -1,28 +1,53 @@
|
||||
import EmberObject from '@ember/object';
|
||||
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default EmberObject.extend({
|
||||
export default Ember.Object.extend({
|
||||
|
||||
// Contract
|
||||
id: null,
|
||||
// blockNumber: null,
|
||||
// blockHash: null,
|
||||
// transactionHash: null,
|
||||
recipientAddress: null,
|
||||
contributorId: null,
|
||||
amount: null,
|
||||
confirmedAt: bignumber('confirmedAtBlock', 'toNumber'),
|
||||
vetoed: null,
|
||||
ipfsHash: null,
|
||||
amount: null,
|
||||
|
||||
creatorAccount: null,
|
||||
|
||||
// IPFS
|
||||
contributor: null,
|
||||
contribution: null,
|
||||
kind: null,
|
||||
description: null,
|
||||
details: null,
|
||||
url: null,
|
||||
ipfsData: '',
|
||||
details: null,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('details', {});
|
||||
}
|
||||
/**
|
||||
* Loads the contribution details from IPFS and sets local instance
|
||||
* properties from it
|
||||
*
|
||||
* @method
|
||||
* @public
|
||||
*/
|
||||
loadDetails(ipfs) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
ipfs.getFile(this.get('ipfsHash')).then(content => {
|
||||
let contributionJSON = JSON.parse(content);
|
||||
let contribution = Ember.Object.create(contributionJSON);
|
||||
|
||||
this.setProperties({
|
||||
kind: contribution.get('kind'),
|
||||
description: contribution.get('description'),
|
||||
url: contribution.get('url')
|
||||
});
|
||||
|
||||
// TODO load details
|
||||
// let details = profile.get('accounts');
|
||||
|
||||
Ember.Logger.debug('[proposal] loaded contribution details', contributionJSON);
|
||||
resolve();
|
||||
}).catch((err) => {
|
||||
Ember.Logger.error('[proposal] error trying to load contribution details', this.get('ipfsHash'), err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
});
|
||||
+110
-15
@@ -1,24 +1,119 @@
|
||||
import EmberObject from '@ember/object';
|
||||
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||
import kreditsValue from 'kredits-web/utils/cps/kredits';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default EmberObject.extend({
|
||||
// Contract
|
||||
id: bignumber('idRaw', 'toString'),
|
||||
account: null,
|
||||
balance: kreditsValue('balanceRaw'),
|
||||
totalKreditsEarned: bignumber('totalKreditsEarnedRaw', 'toNumber'),
|
||||
contributionsCount: bignumber('contributionsCountRaw', 'toNumber'),
|
||||
isCore: false,
|
||||
ipfsHash: null,
|
||||
const {
|
||||
isPresent,
|
||||
} = Ember;
|
||||
|
||||
// IPFS
|
||||
kind: null,
|
||||
export default Ember.Object.extend({
|
||||
|
||||
address: null,
|
||||
name: null,
|
||||
kind: null,
|
||||
url: null,
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
wiki_username: null,
|
||||
ipfsData: ''
|
||||
ipfsHash: null,
|
||||
kredits: null,
|
||||
isCore: false,
|
||||
isCurrentUser: false,
|
||||
|
||||
avatarURL: function() {
|
||||
return `https\:\/\/avatars2.githubusercontent.com/u/${this.get('github_uid')}?v=3&s=128`;
|
||||
}.property('github_uid'),
|
||||
|
||||
|
||||
/**
|
||||
* Loads the contributor's profile data from IPFS and sets local instance
|
||||
* properties from it
|
||||
*
|
||||
* @method
|
||||
* @public
|
||||
*/
|
||||
loadProfile(ipfs) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
ipfs.getFile(this.get('ipfsHash')).then(content => {
|
||||
let profileJSON = JSON.parse(content);
|
||||
let profile = Ember.Object.create(profileJSON);
|
||||
|
||||
this.setProperties({
|
||||
name: profile.get('name'),
|
||||
kind: profile.get('kind')
|
||||
});
|
||||
|
||||
let accounts = profile.get('accounts');
|
||||
let github = accounts.findBy('site', 'github.com');
|
||||
let wiki = accounts.findBy('site', 'wiki.kosmos.org');
|
||||
|
||||
if (isPresent(github)) {
|
||||
this.setProperties({
|
||||
github_username: github.username,
|
||||
github_uid: github.uid,
|
||||
});
|
||||
}
|
||||
if (isPresent(wiki)) {
|
||||
this.setProperties({
|
||||
wiki_username: wiki.username
|
||||
});
|
||||
}
|
||||
|
||||
Ember.Logger.debug('[contributor] loaded contributor profile', profile);
|
||||
resolve();
|
||||
}).catch((err) => {
|
||||
Ember.Logger.error('[contributor] error trying to load contributor profile', this.get('ipfsHash'), err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a JSON-LD object of the contributor, according to
|
||||
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json
|
||||
*
|
||||
* @method
|
||||
* @public
|
||||
*/
|
||||
toJSON() {
|
||||
let contributor = {
|
||||
"@context": "https://schema.kosmos.org",
|
||||
"@type": "Contributor",
|
||||
"kind": this.get('kind'),
|
||||
"name": this.get('name'),
|
||||
"accounts": []
|
||||
};
|
||||
|
||||
if (Ember.isPresent(this.get('url'))) {
|
||||
contributor["url"] = this.get('url');
|
||||
}
|
||||
if (Ember.isPresent(this.get('github_uid'))) {
|
||||
contributor.accounts.push({
|
||||
"site": "github.com",
|
||||
"uid": this.get('github_uid'),
|
||||
"username": this.get('github_username'),
|
||||
"url": `https://github.com/${this.get('github_username')}`
|
||||
});
|
||||
}
|
||||
if (Ember.isPresent(this.get('wiki_username'))) {
|
||||
contributor.accounts.push({
|
||||
"site": "wiki.kosmos.org",
|
||||
"username": this.get('wiki_username'),
|
||||
"url": `https://wiki.kosmos.org/User:${this.get('wiki_username')}`
|
||||
});
|
||||
}
|
||||
|
||||
return contributor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the JSON-LD representation of the model as a string
|
||||
*
|
||||
* @method
|
||||
* @public
|
||||
*/
|
||||
serialize() {
|
||||
return JSON.stringify(this.toJSON());
|
||||
}
|
||||
|
||||
});
|
||||
+92
-22
@@ -1,32 +1,102 @@
|
||||
import EmberObject from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default EmberObject.extend({
|
||||
const {
|
||||
isPresent,
|
||||
isEmpty,
|
||||
} = Ember;
|
||||
|
||||
// Contract
|
||||
id: bignumber('idRaw', 'toString'),
|
||||
creatorAccount: null,
|
||||
contributorId: bignumber('contributorIdRaw', 'toString'),
|
||||
amount: bignumber('amountRaw', 'toNumber'),
|
||||
votesCount: bignumber('votesCountRaw', 'toNumber'),
|
||||
votesNeeded: bignumber('votesNeededRaw', 'toNumber'),
|
||||
export default Ember.Object.extend({
|
||||
|
||||
id: null,
|
||||
creatorAddress: null,
|
||||
recipientAddress: null,
|
||||
recipientName: null,
|
||||
recipientProfile: null,
|
||||
votesCount: null,
|
||||
votesNeeded: null,
|
||||
amount: null,
|
||||
executed: null,
|
||||
ipfsHash: null,
|
||||
|
||||
// Shortcuts
|
||||
isExecuted: alias('executed'),
|
||||
|
||||
// IPFS
|
||||
contribution: null,
|
||||
kind: null,
|
||||
description: null,
|
||||
details: null,
|
||||
url: null,
|
||||
ipfsData: '',
|
||||
details: null,
|
||||
ipfsHash: null,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('details', {});
|
||||
/**
|
||||
* Loads the contribution details from IPFS and sets local instance
|
||||
* properties from it
|
||||
*
|
||||
* @method
|
||||
* @public
|
||||
*/
|
||||
loadContribution(ipfs) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
ipfs.getFile(this.get('ipfsHash')).then(content => {
|
||||
let contributionJSON = JSON.parse(content);
|
||||
let contribution = Ember.Object.create(contributionJSON);
|
||||
|
||||
this.setProperties({
|
||||
kind: contribution.get('kind'),
|
||||
description: contribution.get('description'),
|
||||
url: contribution.get('url')
|
||||
});
|
||||
|
||||
// TODO load details
|
||||
// let details = profile.get('accounts');
|
||||
|
||||
Ember.Logger.debug('[proposal] loaded contribution details', contributionJSON);
|
||||
resolve();
|
||||
}).catch((err) => {
|
||||
Ember.Logger.error('[proposal] error trying to load contribution details', this.get('ipfsHash'), err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a JSON-LD object of the contribution, according to
|
||||
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contribution.json
|
||||
*
|
||||
* @method
|
||||
* @public
|
||||
*/
|
||||
contributionToJSON() {
|
||||
if (isEmpty(this.get('recipientProfile'))) {
|
||||
throw new Error('IPFS hash for recipient profile missing from proposal object');
|
||||
}
|
||||
if (isEmpty(this.get('kind')) || isEmpty(this.get('description'))) {
|
||||
throw new Error('Missing one or more required properties: kind, description');
|
||||
}
|
||||
|
||||
let contribution = {
|
||||
"@context": "https://schema.kosmos.org",
|
||||
"@type": "Contribution",
|
||||
"contributor": {
|
||||
"ipfs": this.get('recipientProfile')
|
||||
},
|
||||
"kind": this.get('kind'),
|
||||
"description": this.get('description'),
|
||||
"details": {}
|
||||
};
|
||||
|
||||
if (isPresent(this.get('url'))) {
|
||||
contribution["url"] = this.get('url');
|
||||
}
|
||||
|
||||
return contribution;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the JSON-LD representation of the contribution as a string
|
||||
*
|
||||
* @method
|
||||
* @public
|
||||
*/
|
||||
serializeContribution() {
|
||||
return JSON.stringify(this.contributionToJSON());
|
||||
}
|
||||
|
||||
});
|
||||
+3
-14
@@ -1,27 +1,16 @@
|
||||
import EmberRouter from '@ember/routing/router';
|
||||
import Ember from 'ember';
|
||||
import config from './config/environment';
|
||||
|
||||
const Router = EmberRouter.extend({
|
||||
const Router = Ember.Router.extend({
|
||||
location: config.locationType,
|
||||
rootURL: config.rootURL
|
||||
});
|
||||
|
||||
Router.map(function() {
|
||||
this.route('dashboard', function() {
|
||||
this.route('contributors', function() {
|
||||
this.route('show', { path: ':id' });
|
||||
});
|
||||
});
|
||||
this.route('spinner');
|
||||
this.route('proposals', function() {
|
||||
this.route('new');
|
||||
});
|
||||
this.route('contributions', function() {
|
||||
this.route('new');
|
||||
});
|
||||
this.route('contributors', function() {
|
||||
this.route('new');
|
||||
this.route('edit', { path: ':id/edit' });
|
||||
});
|
||||
});
|
||||
|
||||
export default Router;
|
||||
@@ -1,31 +1,4 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Route.extend({
|
||||
kredits: service(),
|
||||
|
||||
beforeModel(transition) {
|
||||
const kredits = this.kredits;
|
||||
|
||||
return kredits.setup().then(() => {
|
||||
kredits.get('kredits').preflightChecks().catch((error) => {
|
||||
console.error('Kredits preflight check failed!');
|
||||
console.error(error);
|
||||
});
|
||||
if (kredits.get('accountNeedsUnlock')) {
|
||||
if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
|
||||
transition.retry();
|
||||
}
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log('Error initializing Kredits', error);
|
||||
});
|
||||
},
|
||||
|
||||
afterModel() {
|
||||
return this.kredits.loadInitialData()
|
||||
.then(() => {
|
||||
this.kredits.addContractEventHandlers();
|
||||
});
|
||||
}
|
||||
export default Ember.Route.extend({
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
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.kredits.contributors.findBy('id', params.id);
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
controller.set('attributes', {
|
||||
account: model.account,
|
||||
name: model.name,
|
||||
kind: model.kind,
|
||||
url: model.url,
|
||||
github_username: model.github_username,
|
||||
github_uid: model.github_uid,
|
||||
gitea_username: model.gitea_username,
|
||||
wiki_username: model.wiki_username
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
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);
|
||||
},
|
||||
|
||||
activate () {
|
||||
this.controllerFor('dashboard')
|
||||
.set('showDetailsPane', true);
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
this.controllerFor('dashboard')
|
||||
.set('showDetailsPane', false);
|
||||
}
|
||||
|
||||
});
|
||||
+29
-4
@@ -1,9 +1,34 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import Ember from 'ember';
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
|
||||
export default Route.extend({
|
||||
export default Ember.Route.extend({
|
||||
|
||||
redirect () {
|
||||
this.transitionTo('dashboard');
|
||||
kredits: Ember.inject.service(),
|
||||
|
||||
beforeModel(transition) {
|
||||
const kredits = this.get('kredits');
|
||||
|
||||
if (kredits.get('web3') && kredits.get('web3Provided')) {
|
||||
kredits.get('web3').eth.getAccounts((error, accounts) => {
|
||||
if (error || accounts.length === 0) {
|
||||
if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
|
||||
transition.retry();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
model() {
|
||||
let kredits = this.get('kredits');
|
||||
|
||||
return Ember.RSVP.hash({
|
||||
contributors: kredits.getContributors(),
|
||||
totalSupply: kredits.getValueFromContract('tokenContract', 'totalSupply'),
|
||||
proposals: kredits.getProposals(),
|
||||
contributions: kredits.getContributions(),
|
||||
newContributor: Contributor.create({ kind: 'person' })
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import Ember from 'ember';
|
||||
import Proposal from 'kredits-web/models/proposal';
|
||||
|
||||
const {
|
||||
Route,
|
||||
RSVP,
|
||||
inject: {
|
||||
service
|
||||
}
|
||||
} = Ember;
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
model(params) {
|
||||
const proposal = Proposal.create({
|
||||
recipientAddress: params.recipient,
|
||||
amount: params.amount,
|
||||
url: params.url,
|
||||
kind: params.kind || 'dev',
|
||||
ipfsHash: params.ipfsHash
|
||||
});
|
||||
|
||||
return RSVP.hash({
|
||||
proposal,
|
||||
contributors: this.get('kredits').getContributors()
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
this._super(controller, model.proposal);
|
||||
|
||||
controller.set('contributors', model.contributors);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import Ember from 'ember';
|
||||
import ipfsAPI from 'npm:ipfs-api';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Ember.Service.extend({
|
||||
|
||||
ipfsInstance: null,
|
||||
|
||||
ipfs: function() {
|
||||
if (this.get('ipfsInstance')) {
|
||||
return this.get('ipfsInstance');
|
||||
}
|
||||
let ipfs = ipfsAPI(config.ipfs);
|
||||
this.set('ipfsInstance', ipfs);
|
||||
return ipfs;
|
||||
}.property('ipfsInstance'),
|
||||
|
||||
storeFile(content) {
|
||||
let ipfs = this.get('ipfs');
|
||||
return ipfs.add(new ipfs.Buffer(content)).then(res => {
|
||||
Ember.Logger.debug('[ipfs] stored content in IPFS', content, res[0].hash);
|
||||
return res[0].hash;
|
||||
});
|
||||
},
|
||||
|
||||
getFile(hash) {
|
||||
return this.get('ipfs').cat(hash, { buffer: true }).then(res => {
|
||||
return res.toString();
|
||||
}, err => {
|
||||
Ember.Logger.error('[ipfs] error trying to fetch file', hash, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
+236
-352
@@ -1,400 +1,284 @@
|
||||
import ethers from 'npm:ethers';
|
||||
import Kredits from 'npm:kredits-contracts';
|
||||
import RSVP from 'rsvp';
|
||||
|
||||
import Service from '@ember/service';
|
||||
import EmberObject from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, notEmpty } from '@ember/object/computed';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
import groupBy from 'kredits-web/utils/group-by';
|
||||
import formatKredits from 'kredits-web/utils/format-kredits';
|
||||
|
||||
import Ember from 'ember';
|
||||
import Web3 from 'npm:web3';
|
||||
import config from 'kredits-web/config/environment';
|
||||
import Contributor from 'kredits-web/models/contributor'
|
||||
import Proposal from 'kredits-web/models/proposal'
|
||||
import Contribution from 'kredits-web/models/contribution'
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
import Proposal from 'kredits-web/models/proposal';
|
||||
import Contribution from 'kredits-web/models/contribution';
|
||||
import kreditsContracts from 'npm:kredits-contracts';
|
||||
import uuid from 'npm:uuid';
|
||||
|
||||
const {
|
||||
Service,
|
||||
isPresent,
|
||||
inject: {
|
||||
service
|
||||
}
|
||||
} = Ember;
|
||||
|
||||
export default Service.extend({
|
||||
|
||||
currentBlock: null,
|
||||
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
|
||||
currentUser: null,
|
||||
contributors: null,
|
||||
contributions: null,
|
||||
proposals: null,
|
||||
ipfs: service(),
|
||||
|
||||
currentUserIsContributor: notEmpty('currentUser'),
|
||||
currentUserIsCore: alias('currentUser.isCore'),
|
||||
hasAccounts: notEmpty('currentUserAccounts'),
|
||||
web3Instance: null,
|
||||
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
|
||||
|
||||
accountNeedsUnlock: computed('currentUserAccounts', function() {
|
||||
return this.currentUserAccounts && isEmpty(this.currentUserAccounts);
|
||||
}),
|
||||
web3: function() {
|
||||
if (this.get('web3Instance')) {
|
||||
return this.get('web3Instance');
|
||||
}
|
||||
|
||||
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||
return this.contributions.filter(contribution => {
|
||||
return contribution.confirmedAt > this.currentBlock;
|
||||
});
|
||||
}),
|
||||
let web3Instance;
|
||||
|
||||
contributionsConfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||
return this.contributions
|
||||
.filterBy('vetoed', false)
|
||||
.filter(contribution => {
|
||||
return contribution.confirmedAt <= this.currentBlock;
|
||||
});
|
||||
}),
|
||||
if (typeof window.web3 !== 'undefined') {
|
||||
Ember.Logger.debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
|
||||
web3Instance = window.web3;
|
||||
this.set('web3Provided', true);
|
||||
} else {
|
||||
Ember.Logger.debug('[kredits] Creating new instance from npm module class');
|
||||
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
|
||||
let provider = new Web3.providers.HttpProvider(providerUrl);
|
||||
web3Instance = new Web3(provider);
|
||||
}
|
||||
|
||||
kreditsByContributor: computed('contributionsUnconfirmed.@each.vetoed', 'contributors.[]', function() {
|
||||
const contributionsUnconfirmed = this.contributionsUnconfirmed.filterBy('vetoed', false);
|
||||
const contributionsGrouped = groupBy(contributionsUnconfirmed, 'contributorId');
|
||||
const contributorsWithUnconfirmed = contributionsGrouped.map(c => c.value.toString());
|
||||
const contributorsWithOnlyConfirmed = this.contributors.reject(c => contributorsWithUnconfirmed.includes(c.id))
|
||||
this.set('web3Instance', web3Instance);
|
||||
window.web3 = web3Instance;
|
||||
|
||||
const kreditsByContributor = contributionsGrouped.map(c => {
|
||||
const amountUnconfirmed = c.items.mapBy('amount').reduce((a, b) => a + b);
|
||||
const contributor = this.contributors.findBy('id', c.value.toString());
|
||||
return web3Instance;
|
||||
}.property('web3Instance'),
|
||||
|
||||
return EmberObject.create({
|
||||
contributor: contributor,
|
||||
amountUnconfirmed: amountUnconfirmed,
|
||||
amountConfirmed: contributor.totalKreditsEarned,
|
||||
amountTotal: contributor.totalKreditsEarned + amountUnconfirmed
|
||||
})
|
||||
});
|
||||
currentUserAccounts: function() {
|
||||
return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
|
||||
}.property('web3Provided', 'web3'),
|
||||
|
||||
contributorsWithOnlyConfirmed.forEach(c => {
|
||||
kreditsByContributor.push(EmberObject.create({
|
||||
contributor: c,
|
||||
amountUnconfirmed: 0,
|
||||
amountConfirmed: c.totalKreditsEarned,
|
||||
amountTotal: c.totalKreditsEarned
|
||||
}));
|
||||
})
|
||||
initializeKreditsContract() {
|
||||
let contract = null;
|
||||
|
||||
return kreditsByContributor;
|
||||
}),
|
||||
if (isPresent(config.contractMetadata)) {
|
||||
if (localStorage.getItem('config:networkId')) {
|
||||
config.contractMetadata['networkId'] = localStorage.getItem('config:networkId');
|
||||
}
|
||||
contract = kreditsContracts(this.get('web3'), config.contractMetadata)['Kredits'];
|
||||
} else {
|
||||
contract = kreditsContracts(this.get('web3'))['Kredits'];
|
||||
}
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('contributors', []);
|
||||
this.set('proposals', []);
|
||||
this.set('contributions', []);
|
||||
return contract;
|
||||
},
|
||||
|
||||
// This is called in the application route's beforeModel(). So it is
|
||||
// initialized before everything else, and we can rely on the ethProvider and
|
||||
// the potential currentUserAccounts to be available
|
||||
getEthProvider () {
|
||||
let ethProvider;
|
||||
kreditsContract: function() {
|
||||
if (this.get('kreditsContractInstance')) {
|
||||
return this.get('kreditsContractInstance');
|
||||
}
|
||||
|
||||
return new RSVP.Promise(async (resolve) => {
|
||||
function instantiateWithoutAccount () {
|
||||
console.debug('[kredits] Creating new instance from npm module class');
|
||||
console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`);
|
||||
ethProvider = new ethers.providers.JsonRpcProvider(config.web3ProviderUrl);
|
||||
resolve({
|
||||
ethProvider: ethProvider,
|
||||
ethSigner: null
|
||||
});
|
||||
}
|
||||
let contract = this.initializeKreditsContract();
|
||||
|
||||
async function instantiateWithAccount (web3Provider, context) {
|
||||
console.debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
|
||||
ethProvider = new ethers.providers.Web3Provider(web3Provider);
|
||||
// const network = await ethProvider.getNetwork();
|
||||
ethProvider.listAccounts().then(accounts => {
|
||||
context.set('currentUserAccounts', accounts);
|
||||
const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner();
|
||||
resolve({
|
||||
ethProvider,
|
||||
ethSigner
|
||||
this.set('kreditsContractInstance', contract);
|
||||
// window.Kredits = contract;
|
||||
return contract;
|
||||
}.property('kreditsContractInstance', 'web3'),
|
||||
|
||||
tokenContract: function() {
|
||||
if (this.get('tokenContractInstance')) {
|
||||
return this.get('tokenContractInstance');
|
||||
}
|
||||
|
||||
let contract = kreditsContracts(this.get('web3'), config.contractMetadata)['Token'];
|
||||
this.set('tokenContractInstance', contract);
|
||||
window.Token = contract;
|
||||
return contract;
|
||||
}.property('tokenContractInstance', 'web3'),
|
||||
|
||||
getValueFromContract(contract, contractMethod, ...args) {
|
||||
Ember.Logger.debug('[kredits] read from contract', contract);
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.get(contract)[contractMethod](...args, (err, data) => {
|
||||
if (err) { reject(err); return; }
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getContributorData(i) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.getValueFromContract('kreditsContract', 'contributorAddresses', i).then(address => {
|
||||
this.getValueFromContract('kreditsContract', 'contributors', address).then(person => {
|
||||
this.getValueFromContract('tokenContract', 'balanceOf', address).then(balance => {
|
||||
Ember.Logger.debug('[kredits] person', address, person);
|
||||
|
||||
let contributor = Contributor.create({
|
||||
address: address,
|
||||
ipfsHash: person[2],
|
||||
kredits: balance.toNumber(),
|
||||
isCurrentUser: this.get('currentUserAccounts').includes(address)
|
||||
});
|
||||
|
||||
contributor.loadProfile(this.get('ipfs')).then(
|
||||
() => resolve(contributor),
|
||||
err => reject(err)
|
||||
);
|
||||
});
|
||||
});
|
||||
}).catch(err => reject(err));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
getContributors() {
|
||||
return this.getValueFromContract('kreditsContract', 'contributorsCount').then(contributorsCount => {
|
||||
let contributors = [];
|
||||
|
||||
for(var i = 0; i < contributorsCount.toNumber(); i++) {
|
||||
contributors.push(this.getContributorData(i));
|
||||
}
|
||||
|
||||
if (window.ethereum) {
|
||||
try {
|
||||
// Request account access if needed
|
||||
await window.ethereum.enable();
|
||||
// Acccounts now exposed
|
||||
instantiateWithAccount(window.ethereum, this);
|
||||
} catch (error) {
|
||||
instantiateWithoutAccount();
|
||||
}
|
||||
}
|
||||
// Legacy dapp browsers...
|
||||
else if (window.web3) {
|
||||
instantiateWithAccount(window.web3.currentProvider, this);
|
||||
}
|
||||
// Non-dapp browsers...
|
||||
else {
|
||||
instantiateWithoutAccount();
|
||||
}
|
||||
return Ember.RSVP.all(contributors);
|
||||
});
|
||||
},
|
||||
|
||||
setup () {
|
||||
return this.getEthProvider().then((providerAndSigner) => {
|
||||
let kredits = new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
|
||||
addresses: { Kernel: config.kreditsKernelAddress },
|
||||
apm: config.kreditsApmDomain,
|
||||
ipfsConfig: config.ipfs
|
||||
});
|
||||
return kredits
|
||||
.init()
|
||||
.then(async (kredits) => {
|
||||
this.set('kredits', kredits);
|
||||
this.set('currentBlock', await kredits.provider.getBlockNumber());
|
||||
|
||||
if (this.currentUserAccounts && this.currentUserAccounts.length > 0) {
|
||||
this.getCurrentUser.then((contributorData) => {
|
||||
this.set('currentUser', contributorData);
|
||||
});
|
||||
}
|
||||
|
||||
return kredits;
|
||||
getProposalData(i) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.getValueFromContract('kreditsContract', 'proposals', i).then(p => {
|
||||
let proposal = Proposal.create({
|
||||
id : i,
|
||||
creatorAddress : p[0],
|
||||
recipientAddress : p[1],
|
||||
votesCount : p[2].toNumber(),
|
||||
votesNeeded : p[3].toNumber(),
|
||||
amount : p[4].toNumber(),
|
||||
executed : p[5],
|
||||
url : p[6],
|
||||
ipfsHash : p[7]
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
totalSupply: computed(function() {
|
||||
return this.kredits.Token.functions.totalSupply().then(total => {
|
||||
return formatKredits(total);
|
||||
})
|
||||
}),
|
||||
|
||||
totalKreditsEarned: computed(function() {
|
||||
return this.kredits.Contribution.functions.totalKreditsEarned(true)
|
||||
.then(total => total.toNumber());
|
||||
}),
|
||||
|
||||
|
||||
loadInitialData () {
|
||||
return this.getContributors()
|
||||
.then(contributors => this.contributors.pushObjects(contributors))
|
||||
.then(() => this.getContributions())
|
||||
.then(contributions => this.contributions.pushObjects(contributions))
|
||||
},
|
||||
|
||||
addContributor (attributes) {
|
||||
if (attributes.github_uid) {
|
||||
const uidInt = parseInt(attributes.github_uid);
|
||||
attributes.github_uid = uidInt;
|
||||
}
|
||||
|
||||
console.debug('[kredits] add contributor', attributes);
|
||||
|
||||
return this.kredits.Contributor.add(attributes, { gasLimit: 350000 })
|
||||
.then(data => {
|
||||
console.debug('[kredits] add contributor response', data);
|
||||
});
|
||||
},
|
||||
|
||||
updateContributor (id, attributes) {
|
||||
if (attributes.github_uid) {
|
||||
const uidInt = parseInt(attributes.github_uid);
|
||||
attributes.github_uid = uidInt;
|
||||
}
|
||||
|
||||
console.debug('[kredits] update contributor', attributes);
|
||||
|
||||
return this.kredits.Contributor.updateProfile(id, attributes, { gasLimit: 350000 })
|
||||
.then(data => {
|
||||
console.debug('[kredits] updateProfile response', data);
|
||||
});
|
||||
},
|
||||
|
||||
getContributors () {
|
||||
return this.kredits.Contributor.all()
|
||||
.then((contributors) => {
|
||||
return contributors.map((contributor) => {
|
||||
return Contributor.create(contributor);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
addContribution (attributes) {
|
||||
console.debug('[kredits] add contribution', attributes);
|
||||
|
||||
return this.kredits.Contribution.addContribution(attributes, { gasLimit: 300000 })
|
||||
.then(data => {
|
||||
console.debug('[kredits] add contribution response', data);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId);
|
||||
const contribution = Contribution.create(attributes);
|
||||
// TODO receive from wrapper
|
||||
contribution.set('confirmedAtBlock', data.blockNumber + 40320);
|
||||
this.contributions.pushObject(contribution);
|
||||
return contribution;
|
||||
});
|
||||
},
|
||||
|
||||
addProposal (attributes) {
|
||||
console.debug('[kredits] add proposal', attributes);
|
||||
|
||||
return this.kredits.Proposal.addProposal(attributes)
|
||||
.then((data) => {
|
||||
console.debug('[kredits] add proposal response', data);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId);
|
||||
return Proposal.create(attributes);
|
||||
});
|
||||
},
|
||||
|
||||
getProposals () {
|
||||
return this.kredits.Proposal.all()
|
||||
.then((proposals) => {
|
||||
return proposals.map((proposal) => {
|
||||
proposal.contributor = this.contributors.findBy('id', proposal.contributorId.toString());
|
||||
return Proposal.create(proposal);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getContributions () {
|
||||
return this.kredits.Contribution.all({page: {size: 200}})
|
||||
.then(contributions => {
|
||||
return contributions.map(contribution => {
|
||||
contribution.contributor = this.contributors.findBy('id', contribution.contributorId.toString());
|
||||
return Contribution.create(contribution);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
vote (proposalId) {
|
||||
console.debug('[kredits] vote for', proposalId);
|
||||
|
||||
return this.kredits.Proposal.functions.vote(proposalId)
|
||||
.then(data => {
|
||||
console.debug('[kredits] vote response', data);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
veto (contributionId) {
|
||||
console.debug('[kredits] veto against', contributionId);
|
||||
|
||||
return this.kredits.Contribution.functions.veto(contributionId, { gasLimit: 300000 })
|
||||
.then(data => {
|
||||
console.debug('[kredits] veto response', data);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
getCurrentUser: computed('kredits.provider', function() {
|
||||
if (isEmpty(this.currentUserAccounts)) {
|
||||
return RSVP.resolve();
|
||||
}
|
||||
return this.kredits.Contributor
|
||||
.functions.getContributorIdByAddress(this.get('currentUserAccounts.firstObject'))
|
||||
.then((id) => {
|
||||
// check if the user is a contributor or not
|
||||
if (id === 0) {
|
||||
return RSVP.resolve();
|
||||
if (proposal.get('ipfsHash')) {
|
||||
proposal.loadContribution(this.get('ipfs')).then(
|
||||
() => resolve(proposal),
|
||||
err => reject(err)
|
||||
);
|
||||
} else {
|
||||
return this.kredits.Contributor.getById(id);
|
||||
Ember.Logger.warn('[kredits] proposal from blockchain is missing IPFS hash', proposal);
|
||||
resolve(proposal);
|
||||
}
|
||||
}).catch(err => reject(err));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
getProposals() {
|
||||
return this.getValueFromContract('kreditsContract', 'proposalsCount').then(proposalsCount => {
|
||||
let proposals = [];
|
||||
|
||||
for(var i = 0; i < proposalsCount.toNumber(); i++) {
|
||||
proposals.push(this.getProposalData(i));
|
||||
}
|
||||
|
||||
return Ember.RSVP.all(proposals);
|
||||
});
|
||||
},
|
||||
|
||||
vote(proposalId) {
|
||||
Ember.Logger.debug('[kredits] vote for', proposalId);
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.get('kreditsContract').vote(proposalId, (err, data) => {
|
||||
if (err) { reject(err); return; }
|
||||
Ember.Logger.debug('[kredits] vote response', data);
|
||||
resolve(data);
|
||||
});
|
||||
}),
|
||||
|
||||
findProposalById(proposalId) {
|
||||
return this.proposals.findBy('id', proposalId.toString());
|
||||
});
|
||||
},
|
||||
|
||||
// Contract events
|
||||
addContractEventHandlers () {
|
||||
this.kredits.Contributor
|
||||
.on('ContributorProfileUpdated', this.handleContributorChange.bind(this))
|
||||
.on('ContributorAccountUpdated', this.handleContributorChange.bind(this))
|
||||
.on('ContributorAdded', this.handleContributorChange.bind(this))
|
||||
addContributor(contributor) {
|
||||
Ember.Logger.debug('[kredits] add contributor', contributor);
|
||||
|
||||
this.kredits.Contribution
|
||||
.on('ContributionVetoed', this.handleContributionVetoed.bind(this))
|
||||
contributor.setProperties({
|
||||
kredits: 0,
|
||||
isCurrentUser: this.get('currentUserAccounts').includes(contributor.address)
|
||||
});
|
||||
|
||||
this.kredits.Proposal
|
||||
.on('ProposalCreated', this.handleProposalCreated.bind(this))
|
||||
.on('ProposalVoted', this.handleProposalVoted.bind(this))
|
||||
.on('ProposalExecuted', this.handleProposalExecuted.bind(this));
|
||||
let id = uuid.v4();
|
||||
|
||||
this.kredits.Token
|
||||
.on('Transfer', this.handleTransfer.bind(this));
|
||||
},
|
||||
|
||||
async handleContributorChange (contributorId, ...args) {
|
||||
console.debug('[kredits] Contributor add/update event received for ID', contributorId);
|
||||
console.debug('[kredits] Event data:', args);
|
||||
const contributorData = await this.kredits.Contributor.getById(contributorId);
|
||||
const newContributor = Contributor.create(contributorData);
|
||||
|
||||
const oldContributor = this.contributors.findBy('id', contributorId.toString());
|
||||
if (oldContributor) {
|
||||
console.debug('[kredits] old contributor', oldContributor);
|
||||
this.contributors.removeObject(oldContributor);
|
||||
}
|
||||
|
||||
console.debug('[kredits] new contributor', newContributor);
|
||||
this.contributors.pushObject(newContributor);
|
||||
},
|
||||
|
||||
handleContributionVetoed (contributionId) {
|
||||
console.debug('[kredits] ContributionVetoed event received for ', contributionId);
|
||||
const contribution = this.contributions.findBy('id', contributionId);
|
||||
console.debug('[kredits] contribution', contribution);
|
||||
|
||||
if (contribution) {
|
||||
contribution.set('vetoed', true);
|
||||
}
|
||||
},
|
||||
|
||||
handleProposalCreated (proposalId) {
|
||||
let proposal = this.findProposalById(proposalId);
|
||||
|
||||
if (proposal) {
|
||||
console.debug('[events] proposal exists, not adding from event');
|
||||
return;
|
||||
}
|
||||
|
||||
this.kredits.Proposal.getById(proposalId)
|
||||
.then((proposal) => {
|
||||
proposal.contributor = this.contributors.findBy('id', proposal.contributorId.toString());
|
||||
this.proposals.pushObject(Proposal.create(proposal));
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.get('ipfs').storeFile(contributor.serialize()).then(ipfsHash => {
|
||||
contributor.set('ipfsHash', ipfsHash);
|
||||
this.get('kreditsContract').addContributor(contributor.address, contributor.name, contributor.ipfsHash, contributor.isCore, id, (err, data) => {
|
||||
if (err) { reject(err); return; }
|
||||
Ember.Logger.debug('[kredits] add contributor response', data);
|
||||
resolve(contributor);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// TODO: We may want to reload that proposal to show the voter as voted
|
||||
handleProposalVoted (proposalId, voterId, totalVotes) {
|
||||
let proposal = this.findProposalById(proposalId);
|
||||
addProposal(proposal) {
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
const {
|
||||
recipientAddress,
|
||||
amount,
|
||||
url
|
||||
} = proposal.getProperties('recipientAddress', 'amount', 'url');
|
||||
|
||||
if (proposal) {
|
||||
proposal.set('votesCount', totalVotes);
|
||||
}
|
||||
this.get('ipfs').storeFile(proposal.serializeContribution()).then(ipfsHash => {
|
||||
this.get('kreditsContract').addProposal(recipientAddress, amount, url, ipfsHash, (err, data) => {
|
||||
if (err) { reject(err); return; }
|
||||
Ember.Logger.debug('[kredits] add proposal response', data);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleProposalExecuted (proposalId, contributorId, amount) {
|
||||
let proposal = this.findProposalById(proposalId);
|
||||
getTokenMintedEvents(fromBlock=0, toBlock='latest') {
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
const minted = this.get('tokenContract')
|
||||
.Minted({}, {fromBlock: fromBlock, toBlock: toBlock});
|
||||
|
||||
if (proposal.get('isExecuted')) {
|
||||
console.debug('[events] proposal already executed, not adding from event');
|
||||
return;
|
||||
}
|
||||
|
||||
proposal.set('executed', true);
|
||||
|
||||
this.contributors
|
||||
.findBy('id', contributorId.toString())
|
||||
.incrementProperty('balance', amount);
|
||||
minted.get((err, res) => {
|
||||
if (err) {
|
||||
Ember.Logger.error('[kredits] Error reading Token#Minted events', res);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
Ember.Logger.warn('[kredits] Token#Minted events', res);
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleTransfer (from, to, value) {
|
||||
value = value.toNumber();
|
||||
getContributions() {
|
||||
// this.getTokenMintedEvents().then(events => {
|
||||
let contributions = [];
|
||||
|
||||
this.contributors
|
||||
.findBy('address', from)
|
||||
.decrementProperty('balance', value);
|
||||
config.fixtures.mintedEvents.forEach(mintedEvent => {
|
||||
contributions.push(this.getContributionData(mintedEvent));
|
||||
});
|
||||
|
||||
this.contributors
|
||||
.findBy('address', to)
|
||||
.incrementProperty('balance', value);
|
||||
return Ember.RSVP.all(contributions);
|
||||
// });
|
||||
},
|
||||
|
||||
getContributionData(mintedEvent) {
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
let contribution = Contribution.create({
|
||||
recipientAddress: mintedEvent.args.recipientAddress,
|
||||
contributorId: mintedEvent.args.contributorId,
|
||||
amount: mintedEvent.args.amount,
|
||||
ipfsHash: mintedEvent.args.reference
|
||||
});
|
||||
|
||||
if (contribution.get('ipfsHash')) {
|
||||
contribution.loadDetails(this.get('ipfs')).then(
|
||||
() => resolve(contribution),
|
||||
err => reject(err)
|
||||
);
|
||||
} else {
|
||||
Ember.Logger.warn('[kredits] contribution from Minted event is missing IPFS hash', contribution);
|
||||
resolve(contribution);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// logKreditsContract: function() {
|
||||
// Ember.Logger.debug('[kredits] kreditsContract', this.get('kreditsContract'));
|
||||
// }.on('init')
|
||||
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
button, input[type=submit], .button {
|
||||
display: inline-block;
|
||||
padding: 0.6rem 2rem;
|
||||
background-color: rgba(22, 21, 40, 0.6);
|
||||
border: 1px solid rgba(22, 21, 40, 1);
|
||||
border-radius: 3px;
|
||||
color: $primary-color;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.1em;
|
||||
|
||||
&+button, &+input[type=submit], &+.button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(22, 21, 40, 0.8);
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.2rem 0.8rem;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: $red;
|
||||
background-color: rgba(40, 21, 21, 0.6);
|
||||
border-color: rgba(40, 21, 21, 1);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(40, 21, 21, 0.8);
|
||||
}
|
||||
&:active, &.active {
|
||||
border-color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
&.green {
|
||||
color: $green;
|
||||
background-color: rgba(21, 40, 21, 0.6);
|
||||
border-color: rgba(21, 40, 21, 1);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(21, 40, 21, 0.8);
|
||||
}
|
||||
&:active, &.active {
|
||||
border-color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,4 @@ $green: #97fb68;
|
||||
$yellow: #fbe468;
|
||||
$red: #fb6868;
|
||||
|
||||
$primary-color: $blue;
|
||||
|
||||
$body-text-color: #fff;
|
||||
|
||||
#topbar {
|
||||
background-color: rgba(255, 0, 255, .2);
|
||||
}
|
||||
|
||||
$primaryColor: $blue;
|
||||
+18
-76
@@ -1,89 +1,31 @@
|
||||
main {
|
||||
padding: 1rem 2rem;
|
||||
|
||||
@include media-max(small) {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
&#dashboard {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-row-gap: 2rem;
|
||||
grid-template-areas:
|
||||
"stats"
|
||||
"contributions";
|
||||
}
|
||||
|
||||
&.center-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
section {
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
main {
|
||||
&#dashboard {
|
||||
grid-column-gap: 4rem;
|
||||
grid-row-gap: 2rem;
|
||||
grid-template-columns: 2fr 4fr;
|
||||
grid-template-areas:
|
||||
"stats contributions";
|
||||
|
||||
&.with-details {
|
||||
grid-column-gap: 3rem;
|
||||
grid-template-columns: 2fr 4fr 2fr;
|
||||
grid-template-areas:
|
||||
"stats contributions details";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main section {
|
||||
margin-bottom: 5rem;
|
||||
|
||||
section {
|
||||
@include outer-container;
|
||||
margin-bottom: 8rem;
|
||||
&:first-of-type {
|
||||
margin-top: 2rem;
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
@include media($mobile) {
|
||||
margin-bottom: 5rem;
|
||||
&:first-of-type {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 3rem;
|
||||
@include span-columns(12);
|
||||
padding-bottom: 3rem;
|
||||
text-align: center;
|
||||
// background-color: purple;
|
||||
|
||||
&.with-nav {
|
||||
display: grid;
|
||||
grid-template-columns: auto 5rem;
|
||||
grid-template-areas:
|
||||
"title" "actions";
|
||||
@include media($mobile) {
|
||||
@include span-columns(10);
|
||||
@include shift(1);
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
display: inline-block;
|
||||
// padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
margin-bottom: 2rem;
|
||||
div.content {
|
||||
@include span-columns(12);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
$breakpoints-max: (
|
||||
small: 600px,
|
||||
medium: 960px,
|
||||
large: 1280px
|
||||
);
|
||||
|
||||
$breakpoints-min: (
|
||||
small: 601px,
|
||||
medium: 961px,
|
||||
large: 1281px
|
||||
);
|
||||
|
||||
@mixin media-max($screen-size) {
|
||||
@if map-has-key($breakpoints-max, $screen-size) {
|
||||
@media (max-width: map-get($breakpoints-max, $screen-size)) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
// Debugging
|
||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
||||
}
|
||||
}
|
||||
|
||||
@mixin media-min($screen-size) {
|
||||
@if map-has-key($breakpoints-min, $screen-size) {
|
||||
@media (min-width: map-get($breakpoints-min, $screen-size)) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
// Debugging
|
||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
||||
}
|
||||
}
|
||||
+38
-39
@@ -2,7 +2,6 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
*:hover, *:active, *:focus {
|
||||
@@ -11,19 +10,23 @@
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@import "mediaqueries";
|
||||
@import "bourbon";
|
||||
@import "settings/neat";
|
||||
@import "neat";
|
||||
@import "settings/breakpoints";
|
||||
@import "layout";
|
||||
@import "colors";
|
||||
|
||||
$font-family-sans: 'Open Sans', sans-serif;
|
||||
|
||||
body {
|
||||
font-family: $font-family-sans;
|
||||
font-weight: 300;
|
||||
background-image: linear-gradient(to bottom, rgba(22, 21, 40, .4), rgba(0, 0, 0, .75)), url('/img/bg.jpg');
|
||||
background-image: url('/img/bg.jpg');
|
||||
background-repeat: none;
|
||||
background-attachment: fixed;
|
||||
color: $body-text-color;
|
||||
font-family: $font-family-sans;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
@@ -40,32 +43,26 @@ h1, h2, h3, h4, h5, input, button {
|
||||
|
||||
section {
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
color: $primary-color;
|
||||
font-size: 2.8rem;
|
||||
color: $primaryColor;
|
||||
@include media($mobile) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&#people {
|
||||
&#contributors {
|
||||
.content {
|
||||
p.stats {
|
||||
margin-bottom: 1rem;
|
||||
padding-top: 3rem;
|
||||
font-size: 1rem;
|
||||
color: white;
|
||||
text-align: center;
|
||||
span.number {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&#contributions-by-type {
|
||||
.chart {
|
||||
width: 50%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@include media-max(small) {
|
||||
width: 75%;
|
||||
@include media($mobile) {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,35 +71,37 @@ section {
|
||||
.actions {
|
||||
padding-top: 3rem;
|
||||
font-size: 1rem;
|
||||
color: $primary-color;
|
||||
color: $primaryColor;
|
||||
text-align: center;
|
||||
@include media-max(small) {
|
||||
@include media($mobile) {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
color: $primaryColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
section {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
button, input[type=submit] {
|
||||
display: inline-block;
|
||||
border: 1px solid rgba(22, 21, 40, 1);
|
||||
background-color: rgba(22, 21, 40, 0.6);
|
||||
color: $primaryColor;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.1em;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(22, 21, 40, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
@import "buttons";
|
||||
@import "forms";
|
||||
|
||||
@import "components/contribution-list";
|
||||
@import "components/contributor-list";
|
||||
@import "components/contributor-profile";
|
||||
@import "components/external-account-link";
|
||||
@import "components/loading-spinner";
|
||||
@import "components/contributor-list";
|
||||
@import "components/add-contributor";
|
||||
@import "components/proposal-list";
|
||||
@import "components/topbar";
|
||||
@import "components/user-avatar";
|
||||
@import "components/contribution-list";
|
||||
@@ -1,28 +1,20 @@
|
||||
section#add-contributor,
|
||||
section#add-contribution,
|
||||
section#add-proposal {
|
||||
section#add-contributor, section#add-proposal {
|
||||
|
||||
form {
|
||||
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&.actions {
|
||||
padding-top: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
text-align: center;
|
||||
a {
|
||||
color: $primary-color;
|
||||
color: $primaryColor;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
input[type=text], select {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
@@ -31,22 +23,17 @@ section#add-proposal {
|
||||
background-color: rgba(22, 21, 40, 0.3);
|
||||
color: #fff;
|
||||
font-size: 1.2rem;
|
||||
transition: border-color 0.1s linear;
|
||||
|
||||
&:focus, &.valid {
|
||||
background-color: rgba(22, 21, 40, 0.6);
|
||||
}
|
||||
&:focus {
|
||||
border-color: $blue;
|
||||
}
|
||||
&::placeholder {
|
||||
@include placeholder {
|
||||
color: rgba(238, 238, 238, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: 0;
|
||||
background-color: rgba(22, 21, 40, 0.6);
|
||||
@@ -1,91 +1,19 @@
|
||||
main section {
|
||||
@include media-max(small) {
|
||||
&#contributions-unconfirmed, &#contributions-confirmed {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quick-filter {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
p {
|
||||
font-size: inherit;
|
||||
padding: 0.2rem 0 0;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: inherit;
|
||||
|
||||
&+ label {
|
||||
margin-left: 3.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul.contribution-list {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: grid;
|
||||
grid-template-columns: auto 5rem 5rem;
|
||||
grid-row-gap: 0.5rem;
|
||||
padding: 0.8rem 1.2rem;
|
||||
// display: block;
|
||||
padding: 0 1.2rem;
|
||||
line-height: 4.2rem;
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
font-size: 1.2rem;
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
|
||||
&:first-of-type {
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
&.confirmed {
|
||||
grid-template-columns: auto 5rem;
|
||||
}
|
||||
|
||||
&.vetoed {
|
||||
grid-template-columns: auto 5rem;
|
||||
text-decoration: line-through;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
p {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
line-height: 2rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: inherit;
|
||||
|
||||
&.title {
|
||||
a {
|
||||
font-size: inherit;
|
||||
text-decoration: none;
|
||||
color: $body-text-color;
|
||||
}
|
||||
a:hover, a:active {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
line-height: 1.4em;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.category {
|
||||
color: $blue;
|
||||
padding-right: 0.2rem;
|
||||
@@ -95,61 +23,17 @@ ul.contribution-list {
|
||||
&.docs { color: $green; }
|
||||
&.ops { color: $purple; }
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
font-size: 0.8rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.votes {
|
||||
font-size: 1rem;
|
||||
color: $primary-color;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
ul.proposal-list {
|
||||
li {
|
||||
grid-template-columns: auto 10rem;
|
||||
grid-row-gap: 0.5rem;
|
||||
|
||||
p {
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.unconfirmed {
|
||||
p {
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.description {
|
||||
grid-column-start: span 1;
|
||||
}
|
||||
|
||||
&.voting {
|
||||
grid-column-start: span 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.description {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
table.contributor-list {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
@@ -16,18 +15,14 @@ table.contributor-list {
|
||||
|
||||
&.metadata {
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
|
||||
&:not(.visible) {
|
||||
border-bottom: none;
|
||||
}
|
||||
transition: all 0.2s ease-out;
|
||||
|
||||
td {
|
||||
padding: 0 1.2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
color: $primaryColor;
|
||||
&:hover, &:active {
|
||||
color: #fff;
|
||||
}
|
||||
@@ -38,6 +33,7 @@ table.contributor-list {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: all 0.2s ease-out;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
@@ -49,7 +45,6 @@ table.contributor-list {
|
||||
|
||||
&.visible {
|
||||
height: auto;
|
||||
visibility: visible;
|
||||
ul {
|
||||
height: auto;
|
||||
}
|
||||
@@ -58,33 +53,30 @@ table.contributor-list {
|
||||
|
||||
td {
|
||||
padding: 0 1.2rem;
|
||||
line-height: 3.6rem;
|
||||
line-height: 4.2rem;
|
||||
|
||||
&.person {
|
||||
text-align: left;
|
||||
font-size: 1.2rem;
|
||||
|
||||
font-size: 1.4rem;
|
||||
img.avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.kredits {
|
||||
text-align: right;
|
||||
.amount {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.symbol {
|
||||
font-size: 0.8rem;
|
||||
font-size: 1rem;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
line-height: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
p.role {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
ul.external-accounts {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
|
||||
&+li {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
.button {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
.external-accounts a {
|
||||
display: inline-block;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
padding: 0.8rem;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: $body-text-color;
|
||||
border-radius: 2rem;
|
||||
|
||||
span.site {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fg {
|
||||
fill: $body-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
svg {
|
||||
.fg {
|
||||
fill: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@
|
||||
margin-top: 12rem;
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
color: $primary-color;
|
||||
color: $primaryColor;
|
||||
|
||||
@include media-max(small) {
|
||||
@include media($mobile) {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
margin-bottom: 2rem;
|
||||
|
||||
#path-comet {
|
||||
fill: $primary-color;
|
||||
fill: $primaryColor;
|
||||
opacity: 0.1;
|
||||
|
||||
animation-name: pulse;
|
||||
|
||||
@@ -4,46 +4,16 @@ ul.proposal-list {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: grid;
|
||||
grid-template-columns: auto 5rem;
|
||||
grid-row-gap: 0.5rem;
|
||||
padding: 1rem 1.2rem;
|
||||
// display: block;
|
||||
padding: 0 1.2rem;
|
||||
line-height: 4.2rem;
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
font-size: 1.2rem;
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
|
||||
&:first-of-type {
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
p {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
line-height: 2rem;
|
||||
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.description {
|
||||
grid-column-start: span 2;
|
||||
}
|
||||
|
||||
&.voting {
|
||||
grid-column-start: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.description {
|
||||
line-height: 1.4em;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.category {
|
||||
color: $blue;
|
||||
padding-right: 0.2rem;
|
||||
@@ -53,62 +23,30 @@ ul.proposal-list {
|
||||
&.docs { color: $green; }
|
||||
&.ops { color: $purple; }
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.votes {
|
||||
font-size: 1rem;
|
||||
color: $primary-color;
|
||||
margin-right: 0.5rem;
|
||||
color: $primaryColor;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
margin-top: 1.1rem;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
padding: 0 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
ul.proposal-list {
|
||||
li {
|
||||
grid-template-columns: auto 10rem;
|
||||
grid-row-gap: 0.5rem;
|
||||
|
||||
p {
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.unconfirmed {
|
||||
p {
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.description {
|
||||
grid-column-start: span 1;
|
||||
}
|
||||
|
||||
&.voting {
|
||||
grid-column-start: span 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.description {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#topbar {
|
||||
padding: 0 1rem;
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
background-color: rgba(0,0,0,.3);
|
||||
|
||||
@include media-min(medium) {
|
||||
padding: 0 2rem;
|
||||
height: 4rem;
|
||||
line-height: 4rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
section#user-account {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
img.avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
&.medium {
|
||||
margin: 0;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
border-radius: 5rem;
|
||||
}
|
||||
|
||||
&.large {
|
||||
margin: 0;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
border-radius: 128px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
$mobile: new-breakpoint(max-width 600px);
|
||||
@@ -0,0 +1,5 @@
|
||||
// $visual-grid: true;
|
||||
// $visual-grid-color: red;
|
||||
// $visual-grid-index: front;
|
||||
// $visual-grid-opacity: 0.5;
|
||||
$max-width: em(500, 12);
|
||||
@@ -1,16 +1,3 @@
|
||||
<header id="topbar">
|
||||
<h1>{{link-to "Kosmos Kredits" "dashboard"}}</h1>
|
||||
|
||||
<section id="user-account">
|
||||
{{#if kredits.hasAccounts }}
|
||||
{{#if kredits.currentUser}}
|
||||
{{kredits.currentUser.name}}
|
||||
{{#if kredits.currentUserIsCore}}(core){{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
Anonymous
|
||||
{{/if}}
|
||||
</section>
|
||||
</header>
|
||||
|
||||
{{outlet}}
|
||||
<main>
|
||||
{{outlet}}
|
||||
</main>
|
||||
@@ -1,13 +0,0 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contribution">
|
||||
<header>
|
||||
<h2>Submit a contribution</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contribution contributors=sortedContributors save=(action "save")}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -1,13 +0,0 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contributor">
|
||||
<header>
|
||||
<h2>Update contributor profile</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contributor attributes=attributes save=(action "save")}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -1,13 +0,0 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contributor">
|
||||
<header>
|
||||
<h2>Add contributor profile</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contributor save=(action "save")}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -1,79 +0,0 @@
|
||||
<main id="dashboard" class={{if showDetailsPane "with-details"}}>
|
||||
|
||||
<div id="stats">
|
||||
<section id="people">
|
||||
<header class="with-nav">
|
||||
<h2>Contributors</h2>
|
||||
{{#if kredits.hasAccounts}}
|
||||
<nav>
|
||||
{{link-to "add" "contributors.new" title="Add contributor profile" class="button small green"}}
|
||||
</nav>
|
||||
{{/if}}
|
||||
</header>
|
||||
<div class="content">
|
||||
{{contributor-list contributorList=kreditsToplist
|
||||
showUnconfirmedKredits=showUnconfirmedKredits}}
|
||||
|
||||
<p class="stats">
|
||||
<span class="number">{{await kredits.totalKreditsEarned}}</span> kredits confirmed and issued to
|
||||
<span class="number">{{contributorsWithKredits.length}}</span> contributors
|
||||
</p>
|
||||
<p class="stats">
|
||||
{{input type="checkbox" id="hide-unnconfirmed-kredits" checked=showUnconfirmedKredits}}
|
||||
<label for="hide-unnconfirmed-kredits">Show unconfirmed kredits in toplist</label>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contributions-by-type">
|
||||
<header>
|
||||
<h2>Contributions by type</h2>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{chart-contributions-by-type contributions=contributions}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="contributions">
|
||||
{{#if contributionsUnconfirmed}}
|
||||
<section id="contributions-unconfirmed">
|
||||
<header class="with-nav">
|
||||
<h2>Latest Contributions</h2>
|
||||
{{#if kredits.hasAccounts}}
|
||||
<nav>
|
||||
<button {{action "toggleQuickFilterUnconfirmed"}} class="small {{if showQuickFilterUnconfirmed "active"}}">filter</button>
|
||||
{{link-to "add" "contributions.new" title="Submit a contribution" class="button small green"}}
|
||||
</nav>
|
||||
{{/if}}
|
||||
</header>
|
||||
<div class="content">
|
||||
{{!-- TODO: We need a better naming for kredits.hasAccounts --}}
|
||||
{{contribution-list contributions=contributionsUnconfirmedSorted
|
||||
vetoContribution=(action "vetoContribution")
|
||||
contractInteractionEnabled=kredits.hasAccounts
|
||||
showQuickFilter=showQuickFilterUnconfirmed}}
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<section id="contributions-confirmed">
|
||||
<header class="with-nav">
|
||||
<h2>Confirmed Contributions</h2>
|
||||
<nav>
|
||||
<button {{action "toggleQuickFilterConfirmed"}} class="small {{if showQuickFilterConfirmed "active"}}">filter</button>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{contribution-list contributions=contributionsConfirmedSorted
|
||||
vetoContribution=(action "vetoContribution")
|
||||
showQuickFilter=showQuickFilterConfirmed}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="details">
|
||||
{{outlet}}
|
||||
</div>
|
||||
|
||||
</main>
|
||||
@@ -1,36 +0,0 @@
|
||||
<section id="contributor-profile">
|
||||
<header>
|
||||
{{user-avatar contributor=model size="medium"}}
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<h2>{{model.name}}</h2>
|
||||
<p class="role">{{roleName}}</p>
|
||||
|
||||
<ul class="external-accounts">
|
||||
{{#each model.accounts as |account|}}
|
||||
<li>
|
||||
{{external-account-link account=account}}
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#if model.url}}
|
||||
<li>
|
||||
<a href={{model.url}} target="_blank" rel="noopener" title="Website">
|
||||
<span class="site">Web</span>
|
||||
{{icon-web-globe}}
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</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>
|
||||
@@ -0,0 +1,55 @@
|
||||
<section id="contributors">
|
||||
<header>
|
||||
<h2>Kosmos Contributors</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{contributor-list contributors=contributorsSorted}}
|
||||
|
||||
<p class="stats">
|
||||
<span class="number">{{model.totalSupply}}</span> kredits issued and distributed among
|
||||
<span class="number">{{contributorsWithKredits.length}}</span> contributors.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{#if proposalsOpen}}
|
||||
<section id="proposals-open">
|
||||
<header>
|
||||
<h2>Open Proposals</h2>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{proposal-list proposals=proposalsOpenSorted
|
||||
confirmAction="confirmProposal"
|
||||
contractInteractionEnabled=contractInteractionEnabled}}
|
||||
|
||||
{{#if contractInteractionEnabled}}
|
||||
<p class="actions">
|
||||
{{#link-to 'proposals.new'}}Create new proposal{{/link-to}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<section id="contributions">
|
||||
<header>
|
||||
<h2>Contributions</h2>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{contribution-list contributions=model.contributions
|
||||
contributors=model.contributors}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="add-contributor">
|
||||
<header>
|
||||
<h2>Add Contributor</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contributor contributors=model.contributors
|
||||
newContributor=model.newContributor
|
||||
contractInteractionEnabled=contractInteractionEnabled}}
|
||||
</div>
|
||||
</section>
|
||||
File renamed without changes.
@@ -4,7 +4,7 @@
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-proposal contributors=minedContributors save=(action "save")}}
|
||||
{{add-proposal proposal=model contributors=contributors onSave=(action 'onSave')}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{loading-spinner}}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { computed } from '@ember/object';
|
||||
import ethers from 'npm:ethers';
|
||||
|
||||
export default function(dependentKey, converterMethod) {
|
||||
return computed(dependentKey, {
|
||||
get () {
|
||||
let value = this.get(dependentKey);
|
||||
if (value && ethers.utils.BigNumber.isBigNumber(value)) {
|
||||
return value[converterMethod]();
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
set (key, value) {
|
||||
const bnValue = ethers.utils.bigNumberify(value);
|
||||
this.set(dependentKey, bnValue);
|
||||
return bnValue[converterMethod]();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { computed } from '@ember/object';
|
||||
import ethers from 'npm:ethers';
|
||||
import formatKredits from 'kredits-web/utils/format-kredits';
|
||||
|
||||
export default function(dependentKey, options = {}) {
|
||||
return computed(dependentKey, {
|
||||
get () {
|
||||
const value = this.get(dependentKey);
|
||||
return formatKredits(value, options);
|
||||
},
|
||||
set (key, value) {
|
||||
const bnValue = ethers.utils.bigNumberify(value);
|
||||
this.set(dependentKey, bnValue);
|
||||
return formatKredits(bnValue, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import ethers from 'npm:ethers';
|
||||
|
||||
export default function(value, options = {}) {
|
||||
let etherValue = ethers.utils.formatEther(value);
|
||||
if (!options.decimals) {
|
||||
etherValue = parseInt(etherValue).toString();
|
||||
}
|
||||
return etherValue;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// Code from https://github.com/HeroicEric/ember-group-by (MIT licensed)
|
||||
//
|
||||
import { A } from '@ember/array';
|
||||
import { get } from '@ember/object';
|
||||
import { isPresent } from '@ember/utils';
|
||||
|
||||
export default function groupBy (collection, property) {
|
||||
let groups = A();
|
||||
let items = collection;
|
||||
|
||||
if (items) {
|
||||
items.forEach(function(item) {
|
||||
let value = get(item, property);
|
||||
let group = groups.findBy('value', value);
|
||||
|
||||
if (isPresent(group)) {
|
||||
get(group, 'items').push(item);
|
||||
} else {
|
||||
group = { property: property, value: value, items: [item] };
|
||||
groups.push(group);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "kredits-web",
|
||||
"dependencies": {
|
||||
"ember": "~2.10.0",
|
||||
"ember-cli-shims": "0.1.3",
|
||||
"bourbon": "^4.2.7",
|
||||
"neat": "^1.8.0"
|
||||
}
|
||||
}
|
||||
+21
-24
@@ -1,11 +1,10 @@
|
||||
/* jshint node: true */
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = function(environment) {
|
||||
|
||||
let ENV = {
|
||||
var ENV = {
|
||||
modulePrefix: 'kredits-web',
|
||||
environment,
|
||||
environment: environment,
|
||||
rootURL: '/',
|
||||
locationType: 'auto',
|
||||
|
||||
@@ -35,30 +34,41 @@ module.exports = function(environment) {
|
||||
]
|
||||
},
|
||||
|
||||
web3ProviderUrl: 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9',
|
||||
contractMetadata: {},
|
||||
|
||||
web3ProviderUrl: 'https://parity.kosmos.org:8545',
|
||||
|
||||
ipfs: {
|
||||
host: 'ipfs.kosmos.org',
|
||||
port: '5444',
|
||||
protocol: 'https',
|
||||
gatewayUrl: 'https://ipfs.kosmos.org/ipfs'
|
||||
protocol: 'https'
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.KREDITS_CONTRACT_ADDR) {
|
||||
ENV.contractMetadata['Kredits'] = { address: process.env.KREDITS_CONTRACT_ADDR };
|
||||
}
|
||||
if (process.env.TOKEN_CONTRACT_ADDR) {
|
||||
ENV.contractMetadata['Token'] = { address: process.env.TOKEN_CONTRACT_ADDR };
|
||||
}
|
||||
if (process.env.WEB3_PROVIDER_URL) {
|
||||
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
|
||||
}
|
||||
ENV.contractMetadata['networkId'] = "17";
|
||||
|
||||
if (environment === 'development') {
|
||||
// ENV.APP.LOG_RESOLVER = true;
|
||||
// ENV.APP.LOG_ACTIVE_GENERATION = true;
|
||||
// ENV.APP.LOG_TRANSITIONS = true;
|
||||
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
||||
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||
ENV.web3ProviderUrl = 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9';
|
||||
|
||||
ENV.ipfs = {
|
||||
host: 'localhost',
|
||||
port: '5001',
|
||||
protocol: 'http',
|
||||
gatewayUrl: 'http://localhost:8080/ipfs'
|
||||
protocol: 'http'
|
||||
};
|
||||
const mintedEvents = JSON.parse(fs.readFileSync('config/fixtures-token-minted.json'));
|
||||
ENV.fixtures = { mintedEvents: mintedEvents };
|
||||
}
|
||||
|
||||
if (environment === 'test') {
|
||||
@@ -70,22 +80,9 @@ module.exports = function(environment) {
|
||||
ENV.APP.LOG_VIEW_LOOKUPS = false;
|
||||
|
||||
ENV.APP.rootElement = '#ember-testing';
|
||||
ENV.APP.autoboot = false;
|
||||
}
|
||||
|
||||
if (environment === 'production') {
|
||||
// here you can enable a production-specific feature
|
||||
ENV.kreditsApmDomain = process.env.KREDITS_APM_DOMAIN || 'open.aragonpm.eth';
|
||||
}
|
||||
|
||||
if (process.env.WEB3_PROVIDER_URL) {
|
||||
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
|
||||
}
|
||||
if (process.env.KREDITS_KERNEL_ADDRESS) {
|
||||
ENV.kreditsKernelAddress = process.env.KREDITS_KERNEL_ADDRESS;
|
||||
}
|
||||
if (process.env.KREDITS_APM_DOMAIN) {
|
||||
ENV.kreditsApmDomain = process.env.KREDITS_APM_DOMAIN;
|
||||
}
|
||||
|
||||
return ENV;
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
[
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x70ba4415fc788d9481b5c2e557a55ce698de15b02eb5fba514a0b33027796c5a",
|
||||
"blockNumber": 46,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x3d4baea1f8a63f5b80491d5e67f2a833a4a6db7427a0c07ca181a852cd29dedb",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||
"contributorId": 1,
|
||||
"amount": "150",
|
||||
"reference": "QmWj7XC6sSu1D7zmcH1tHFV9711P96m1z4qFP8yALZMWem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x0112ca39f27dbc17ec7478336a9ba63ede0930cad0ead68b6fd441269950d6cc",
|
||||
"blockNumber": 47,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x8d1277270c51afdb9b6345015de176260412d7a649dfb00307dc8e7c513a250e",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||
"contributorId": 2,
|
||||
"amount": "150",
|
||||
"reference": "QmSfgMxbBHNouW9vwD8DHkfdbMjAuMpv1fpBB6uPCazRCA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x98fc834495394b8b26520fcd503508b953f7977a89df49a2a2d6711f2aa4d0c6",
|
||||
"blockNumber": 48,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x8f21b1df6ea33520c437bf9252a611603f52dca397ba93a3e2680da2c31d28e1",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||
"contributorId": 1,
|
||||
"amount": "1000",
|
||||
"reference": "QmNbmsh3Rozksv6a5cTQM5mapQNpksU4Zcy9zMLcWsJoUf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xf34ce94de4496441cb3972525f4010370b6957bbc09deba15501a169d063fa59",
|
||||
"blockNumber": 50,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x328ffbe2b34ba80c861445768b6069bc43eb511e196e7c22042a1f714aad78fa",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||
"contributorId": 3,
|
||||
"amount": "150",
|
||||
"reference": "QmYE2ipsfbk9fwLJQ5wBrc9WKtneHjEGfV2RfngsxdKe1g"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xf6f8c1686497c7dc0ae4af2c87d7547b75bdffb960dce52a62bf74c1976980fb",
|
||||
"blockNumber": 51,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x694796b4b1bd82cadea6471eb2f718323ca85fed00ddb9b8a73524dec31141e6",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||
"contributorId": 2,
|
||||
"amount": "50",
|
||||
"reference": "QmZZVbZN6hQTA5ZxrUywehdC2cCHkD6wvkVbVWJP21UmgR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x771045eecdd57959e9cb9ac613df988eeee8b800e8f9edebfdf01a8f1a4f3c5d",
|
||||
"blockNumber": 52,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0xaf9f073f80b8b492a7e822cfd38e5d13474a99aaef9be0c3bc0ffaf8c653a5f4",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||
"contributorId": 1,
|
||||
"amount": "50",
|
||||
"reference": "QmbrszXGexzCZaWer6nFxjj4EXp5y4WT3WYPrbeNooQMyy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x86ee58b0e36f3d199b0d241f3efb868a7276fdf084cff48ef045bdfe235c0506",
|
||||
"blockNumber": 53,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x9c7076b2fa49988fe706639376413bc79e4c5549c0145f00d7f27af6088c7128",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||
"contributorId": 1,
|
||||
"amount": "150",
|
||||
"reference": "QmV5wSKLRBdXXbRuxWneQ1tDZP75Dpjv3adQHGye8CemEb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xc2b3f860e0b8d4037529eca9b7d539332b3cc08741165378ac8368c22c6a18b3",
|
||||
"blockNumber": 54,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0xfd2f5439e265e8899be5e2e18a9df56f4706e618d5459aa8f206aec091f7c0d9",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||
"contributorId": 2,
|
||||
"amount": "50",
|
||||
"reference": "QmNp6ggpaR3yNuEWZbCttnUBjshQonFWE766iyVLAKfq7M"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x860667b8f67ae22ac97bd67d9ccb245eb98312eb5cb22303d1a8a383defbb5fc",
|
||||
"blockNumber": 60,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0xbe153f64a31a1669ef1bffa16b0aeb1657b1b9fe779c38f6455d05fe3d96fd5b",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||
"contributorId": 1,
|
||||
"amount": "50",
|
||||
"reference": "QmY2eBgBtFir4edYbw91KQNidMNHnCCem2LCJpTJ56Q87U"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x2c5da053f98cb5462a8aa4fa6bb2a5e7bb324baa404fbbd8ea25fdaaaac1b0db",
|
||||
"blockNumber": 65,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x1df5a2819a6312d170c323b517b03a7186a789845ecfe896d37de6fa482b924c",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||
"contributorId": 3,
|
||||
"amount": "50",
|
||||
"reference": "QmeVbzGiXmPz4qgi5PfktZi2Esb4EVPQZLRnWnUPVatJgF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x0e478ae9cf6e09e2ca8c4413c874f39decfb9d51c976467ef14d99efa79c9bcb",
|
||||
"blockNumber": 75,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0xf16ace4f72c599c3b738bdaa4518f59a47dcdc304a8f24308c5d121ae0ae5b97",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0x0536806df512d6cdde913cf95c9886f65b1d3462",
|
||||
"contributorId": 1,
|
||||
"amount": "100",
|
||||
"reference": "QmS3cywqGhHh6QBLnyBWXPTas2UnHEW8ZV8zZy6pVw95Zx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0x31e91da869e9b87949e75ee14879005b172df96267f9ffd76d5024a46fe48a3c",
|
||||
"blockNumber": 76,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x735eb48c81f6868b886d3afede6b031323e78b221f3e2653ad2186c3e4e1abe9",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||
"contributorId": 2,
|
||||
"amount": "500",
|
||||
"reference": "QmP5RPzjgRdtq9Uj6tXxgMfA1GkY9Fh5ghXpDYBtc9YwMp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xd41a4c3caae9e52632eba047d038e430b50920138e3a625b50a0a887e719a487",
|
||||
"blockNumber": 77,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x075beb007d18b38344599fe5b4dc315633aebedf08201ab9537f7d407811b0a2",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||
"contributorId": 2,
|
||||
"amount": "1000",
|
||||
"reference": "QmTxwugByKfyPX2czyVDGhXK3NLZkA3uh2DbNnaAMVrVJB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xcb3863eb681873a2c5954e07e085d4004a2824fcd17364b647e2fff1bea3ad4b",
|
||||
"blockNumber": 78,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0xdf680c7a0bc60ef02619def582f68230bdd0fd8a945c9006841212ebcd6b80db",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||
"contributorId": 3,
|
||||
"amount": "300",
|
||||
"reference": "QmYXyGSK6RyXQHNPHAADjpJ4P3mrz5Lf1bNnrp2yRaDxNo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xe322d5292506ef1c4c915e875a4e8c1882d154fd9b711522809045bbb884e11d",
|
||||
"blockNumber": 89,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x5d90bf8c74b18b602e8b04fbca8678033589f6527b1ced08c0ee7d6c96f71949",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xb2d955733e6a470533f68f72d0af442070f24f55",
|
||||
"contributorId": 1,
|
||||
"amount": "300",
|
||||
"reference": "QmZdUuq4NMz2S5bD3m9h9MNNsbTVeFVHzQss4uS1ZqPLsw"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xbb84cfc9c3298b045d4ae70afa9cc966db578b00e897f3d6988b08deeca0e3d9",
|
||||
"blockNumber": 120,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0xf91a038b17cfd9b5eac8f4474d5e4d9a5034dead51fc2fffc9e88afdefd9129c",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||
"contributorId": 1,
|
||||
"amount": "500",
|
||||
"reference": "QmdQWivxwjuEHEYVu3ezZCzEWps6wEvQ8ePUHbQ7htSN8V"
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||
"blockHash": "0xc85a520ffc16b5f84d5f5bd29478d0871a045356e598c72be9a44ae3e6826bb5",
|
||||
"blockNumber": 121,
|
||||
"logIndex": 0,
|
||||
"transactionHash": "0x59a2e3e1f81b8113b3b71eba26f28f75ba18d60b6dda6a4a216d566d7d68a31d",
|
||||
"transactionIndex": 0,
|
||||
"transactionLogIndex": "0x0",
|
||||
"type": "mined",
|
||||
"event": "Minted",
|
||||
"args": {
|
||||
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||
"contributorId": 1,
|
||||
"amount": "50",
|
||||
"reference": "QmfDG3vCAgQsqkxkb6jCnLqw11wjw3uUpiZRbRBLZ9GpYi"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"jquery-integration": true
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const browsers = [
|
||||
'last 1 Chrome versions',
|
||||
'last 1 Firefox versions',
|
||||
'last 1 Safari versions'
|
||||
];
|
||||
|
||||
const isCI = !!process.env.CI;
|
||||
const isProduction = process.env.EMBER_ENV === 'production';
|
||||
|
||||
if (isCI || isProduction) {
|
||||
browsers.push('ie 11');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
browsers
|
||||
};
|
||||
+10
-12
@@ -1,17 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
||||
/*jshint node:true*/
|
||||
/* global require, module */
|
||||
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
||||
|
||||
module.exports = function(defaults) {
|
||||
let app = new EmberApp(defaults, {
|
||||
// sassOptions: {
|
||||
// includePaths: [
|
||||
// 'node_modules/bourbon/app/assets/stylesheets'
|
||||
// ]
|
||||
// },
|
||||
sourcemaps: {
|
||||
enabled: true,
|
||||
extensions: ['js']
|
||||
var app = new EmberApp(defaults, {
|
||||
// Add options here
|
||||
sassOptions: {
|
||||
includePaths: [
|
||||
'bower_components/bourbon/app/assets/stylesheets',
|
||||
'bower_components/neat/app/assets/stylesheets'
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user