Compare commits
363 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f1ec4887e | |||
| fe16a605c1 | |||
| 9cf743b02a | |||
| d7294d9d7f | |||
| d8a9886442 | |||
| d83d671580 | |||
| 0bbca079c5 | |||
| 8bd7a77d44 | |||
| a73a4da575 | |||
| 8cc1b02d19 | |||
| 6b49ca26c0 | |||
| ffe555e2ee | |||
| aa98b35796 | |||
| b2bbbfd010 | |||
| edd7ffd3c5 | |||
| abb7d95804 | |||
| 0e0b1afe3a | |||
| 29a6d79d38 | |||
| 07c579850e | |||
| 091ebf4b6a | |||
| b4e89492b4 | |||
| f5c89fd567 | |||
| 1fbf8f0769 | |||
| cc8952f844 | |||
| 25ea20e63b | |||
| e60651858d | |||
| 24390c6503 | |||
| c1d5c90745 | |||
| 3d20bce5e3 | |||
| 3fe8043c76 | |||
| bad86232b4 | |||
| 7eb14ac4e2 | |||
| 8b0cd8c986 | |||
| 5064a472f2 | |||
| afc5a5fe50 | |||
| f9f6786f95 | |||
| 03f31e2d20 | |||
| 1c1f772ff5 | |||
| 58bd729acb | |||
| 1ea5305e6e | |||
| e84a1cbc4e | |||
| 29115d9b16 | |||
| 409d5e33f9 | |||
| 883b5dc415 | |||
| 16ee3b6b6b | |||
| 4efd6f8f88 | |||
| af9951ca5a | |||
| c30c6c2b36 | |||
| 8bd4c6e6e1 | |||
| 9a98b37bb6 | |||
| fad10ec1ea | |||
| cec87f6caf | |||
| 22a9786168 | |||
| 18be60da9b | |||
| 4b6c79345b | |||
| f07100b7ea | |||
| 7083da367d | |||
| 8720a0faab | |||
| d8b1b3d777 | |||
| 4c2d95f8f1 | |||
| 9386c30d09 | |||
| 6638691c9f | |||
| d2bb70e8a1 | |||
| ef45d55521 | |||
| f86c53de89 | |||
| 82b275caaa | |||
| e524e5558d | |||
| 6593005566 | |||
| cf77f20d41 | |||
| 67ee2d9a74 | |||
| 48e5ddbc02 | |||
| e1737392a7 | |||
| 00b7b380b9 | |||
| 9821c8b2ea | |||
| c6a37f7e94 | |||
| f25c74c7e9 | |||
| 1c49d56b19 | |||
| 109ffd2898 | |||
| 7d5b93a62c | |||
| 6c0cb1a29c | |||
| d444b86ca8 | |||
| 185d7c58c2 | |||
| 283ec1d48a | |||
| f32a34a702 | |||
| afa09f4b8f | |||
| 24a3d8beb4 | |||
| 2a7ec1073f | |||
| d02fb4716a | |||
| 49bfad51b6 | |||
| 25ead15014 | |||
| 85a9d1c73b | |||
| 247fc8785a | |||
| efd1b371f9 | |||
| 3c73ddd443 | |||
| 29e9df97fb | |||
| fa95376fda | |||
| 028391386b | |||
| de937aceee | |||
| 2f4807cdda | |||
| d1791cadfe | |||
| 83fd9a94b0 | |||
| 562a8d159b | |||
| 1000dd12bc | |||
| 6f4afaf84a | |||
| 5de33a18f5 | |||
| 2aae2a8f90 | |||
| 6dcbd1efbf | |||
| a2ddb2313d | |||
| 6de7f64d3d | |||
| d0cae59070 | |||
| 6bd87ae0de | |||
| 0380ea3e98 | |||
| 9dba432542 | |||
| da608a0012 | |||
| 0af78e3dbd | |||
| 5db628311e | |||
| aa28a14d04 | |||
| 9c4e431e3b | |||
| 8984b1ecb7 | |||
| f05b0bbd8b | |||
| bff611f866 | |||
| b17e4c0a63 | |||
| f0b75d96db | |||
| 190975b981 | |||
| e4c9a88eca | |||
| 1f8fa367a5 | |||
| 5202fe0f8e | |||
| f202b34d28 | |||
| 07fa07eb22 | |||
| 137f9ae8bf | |||
| e44a8efa4a | |||
| a7b288c25f | |||
| 3dbcba5eb2 | |||
| bd2a640be0 | |||
| 889f2c05fc | |||
| c3f2afcaf5 | |||
| 32c85ea56b | |||
| 97210df0ba | |||
| 88be3525b5 | |||
| 7da7de1edc | |||
| 7592d67d69 | |||
| b75ac5c8b9 | |||
| 2954955e39 | |||
| 1fb20b1d08 | |||
| bfa0b350ac | |||
| 487013f301 | |||
| b16bb2d93a | |||
| 64997d74e9 | |||
| 297ed4ee45 | |||
| b4a094bc89 | |||
| e43706e2a6 | |||
| 2befc91d90 | |||
| 7a35f5177c | |||
| 6efc3ea790 | |||
| ef0f3ffbfb | |||
| 1e14cc695e | |||
| c074ccf460 | |||
| 5436469128 | |||
| 32714efeb9 | |||
| cf777571dc | |||
| 44b4340a91 | |||
| 24e3171311 | |||
| 575c1a11c2 | |||
| 47b8421d35 | |||
| 38d853a272 | |||
| 0320d6149f | |||
| ba9b3530fa | |||
| e78832e2c8 | |||
| 804060ad34 | |||
| 840519a437 | |||
| 7c54ebcbd0 | |||
| d07c0d67bf | |||
| a0ee2f7bb8 | |||
| d070288e17 | |||
| 681f80f6ab | |||
| eb5c87308a | |||
| 3e7fe1b5be | |||
| 552ca3026d | |||
| ba50e5a878 | |||
| acd1ce28f4 | |||
| 1b2b1bd447 | |||
| 7083d2369d | |||
| 8fed06b494 | |||
| 75650c19ab | |||
| ade47dadde | |||
| eaec617756 | |||
| 2199394abc | |||
| 93a18a8a20 | |||
| 2190b7af68 | |||
| 7da4956f1e | |||
| 31e27865bb | |||
| 967f213cba | |||
| 0e556aa794 | |||
| e2ac6099e3 | |||
| a4088e7acf | |||
| 550bd48fdb | |||
| f0f82a96fb | |||
| 8d303ea5af | |||
| c0b5c36115 | |||
| ba3c707da3 | |||
| b09f19f399 | |||
| 539f32911d | |||
| e009caaf25 | |||
| 1761ffbd3b | |||
| 33d6499dd4 | |||
| 3839eb2be4 | |||
| a04ab09ed6 | |||
| 89d6b920b1 | |||
| d425ed6b9e | |||
| e4215557c8 | |||
| fcc4045a4e | |||
| 5bcce71b36 | |||
| c9abec8a46 | |||
| 62e195d186 | |||
| e5f69a5e9a | |||
| e31ff492d5 | |||
| 99bbcda709 | |||
| 8aa941e704 | |||
| fdda30da67 | |||
| 399ee27af9 | |||
| d2c43b9ef1 | |||
| 7e6c6a037d | |||
| e1f19a1832 | |||
| 7b5cea3613 | |||
| 5d875d23e2 | |||
| e24fc6c81d | |||
| 72c6266474 | |||
| 4a711788f6 | |||
| c373c901a2 | |||
| 32846194b6 | |||
| 1a5d33b1f0 | |||
| 0e330a6529 | |||
| e9f0638ca2 | |||
| ce8fef79dc | |||
| b9fa0e2d60 | |||
| 41a04d6b53 | |||
| e527099b00 | |||
| 7e04cef964 | |||
| cb1208acaa | |||
| f985ce6402 | |||
| f54a36f240 | |||
| 4182f0993c | |||
| 28f7466595 | |||
| 6cfd4240de | |||
| 6e24015f76 | |||
| 5390c5cea8 | |||
| adcc81ba52 | |||
| 31a1f21dcd | |||
| b7316e15a2 | |||
| ad92ffa447 | |||
| 45044c0f61 | |||
| cc7c47cbab | |||
| 27f4006f05 | |||
| 576313a7b0 | |||
| 13c3feaa69 | |||
| a0dd8b925b | |||
| 21f187cac2 | |||
| d8951caddb | |||
| 206c657b98 | |||
| 6e262537a4 | |||
| 0587559939 | |||
| 939baec1a8 | |||
| 429f670c12 | |||
| b21d2ad864 | |||
| 8f841522a3 | |||
| fa7db66f4e | |||
| 499b22af15 | |||
| f992fb10bd | |||
| 603224e906 | |||
| bbea07b2a7 | |||
| b9d78efaff | |||
| 1a58099413 | |||
| e2d02d357c | |||
| 0812ff3467 | |||
| 6dafc26deb | |||
| a418ddbe52 | |||
| 6a5eb5a313 | |||
| 6354b03717 | |||
| 5d6b4a5255 | |||
| 3f0b1ccbcd | |||
| f00ecfd9ab | |||
| ee0d3370b1 | |||
| 73d7ed52dd | |||
| 6a31b0d579 | |||
| 869c4c0ae9 | |||
| 4be696ea62 | |||
| 83a0d56314 | |||
| 0d1418e595 | |||
| 7db521a72a | |||
| e7ff606891 | |||
| 60de3d8a31 | |||
| f12740dfa8 | |||
| 020a1c5682 | |||
| 576e2741a9 | |||
| bc19363bf9 | |||
| 72cf4b495e | |||
| b1db64a07d | |||
| 22affa7663 | |||
| b125d941ba | |||
| 9b37f130f3 | |||
| 993cd85e4a | |||
| e51586a78c | |||
| 8aa86fa553 | |||
| 510f7de693 | |||
| 918ae5b2d1 | |||
| 6263254a1b | |||
| 99e921c302 | |||
| 7f2f781a77 | |||
| 3571cc633e | |||
| 2011636173 | |||
| cbb15f4d6f | |||
| 33072900e9 | |||
| c98a50cd42 | |||
| 1f41dbcae5 | |||
| a918719b8f | |||
| 7b91e64af4 | |||
| 0afbeea01b | |||
| 0510cf2389 | |||
| c893f91ce2 | |||
| eced2f423f | |||
| 6eeb505701 | |||
| 4cb3f5d8ba | |||
| 372a8a9b2d | |||
| a714601c63 | |||
| 240d446b97 | |||
| 33e81a7668 | |||
| f8b75cca61 | |||
| 32e1bbd617 | |||
| 1de26004ab | |||
| 0a66c19b0a | |||
| 40d6b9905a | |||
| 228825053c | |||
| eda0f7a467 | |||
| 3052c9dfc5 | |||
| dbb4094eab | |||
| f875cd7b91 | |||
| 45f17100a2 | |||
| dda5a8d350 | |||
| 4f57ceaee9 | |||
| 2405e3e118 | |||
| d7aaadea2b | |||
| 8d07c7d741 | |||
| 2b260bccd5 | |||
| 7299394809 | |||
| 0b7afc0dd5 | |||
| f57e90941c | |||
| be6c5574ff | |||
| 859cca3820 | |||
| ba1cd1cd49 | |||
| a1f73b0307 | |||
| 3be14b945a | |||
| 675dff7314 | |||
| 2ff76c902b | |||
| 642015700e | |||
| 969ef8d1ed | |||
| b5f8216f7f | |||
| e6740d9b4f | |||
| 2e61b4c8ad | |||
| 62a0733a11 | |||
| 09aa8a5669 | |||
| 01c1a9c804 | |||
| 0a2f44cec8 | |||
| e7fa188797 |
+1
-1
@@ -5,5 +5,5 @@
|
|||||||
|
|
||||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
Setting `disableAnalytics` to true will prevent any data from being sent.
|
||||||
*/
|
*/
|
||||||
"disableAnalytics": false
|
"disableAnalytics": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
+16
-7
@@ -1,17 +1,26 @@
|
|||||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
/dist/
|
||||||
/tmp
|
/tmp/
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/bower_components/
|
||||||
/bower_components
|
/node_modules/
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
/.env*
|
||||||
|
/.pnp*
|
||||||
/.sass-cache
|
/.sass-cache
|
||||||
/connect.lock
|
/connect.lock
|
||||||
/coverage/*
|
/coverage/
|
||||||
/libpeerconnection.log
|
/libpeerconnection.log
|
||||||
npm-debug.log*
|
/npm-debug.log*
|
||||||
testem.log
|
/testem.log
|
||||||
|
/yarn-error.log
|
||||||
|
.tm_properties
|
||||||
|
|
||||||
|
# ember-try
|
||||||
|
/.node_modules.ember-try/
|
||||||
|
/bower.json.ember-try
|
||||||
|
/package.json.ember-try
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: 'recommended',
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'simple-unless': false
|
||||||
|
}
|
||||||
|
};
|
||||||
+17
-10
@@ -1,25 +1,32 @@
|
|||||||
---
|
---
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "6"
|
- "10"
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
|
dist: xenial
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
on_success: never
|
||||||
|
on_failure: always
|
||||||
|
|
||||||
|
addons:
|
||||||
|
chrome: stable
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.npm
|
- $HOME/.npm
|
||||||
- $HOME/.cache # includes bowers cache
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
# See https://git.io/vdao3 for details.
|
||||||
|
- JOBS=1
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- npm config set spin false
|
- 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:
|
script:
|
||||||
|
- npm run lint:hbs
|
||||||
|
- npm run lint:js
|
||||||
- npm test
|
- npm test
|
||||||
|
|||||||
@@ -10,22 +10,24 @@ A short introduction of this app could easily go here.
|
|||||||
You will need the following things properly installed on your computer.
|
You will need the following things properly installed on your computer.
|
||||||
|
|
||||||
* [Git](https://git-scm.com/)
|
* [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/)
|
* [Ember CLI](https://ember-cli.com/)
|
||||||
* [PhantomJS](http://phantomjs.org/)
|
* [Google Chrome](https://google.com/chrome/)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
* `git clone <repository-url>` this repository
|
* `git clone git@github.com:67P/kredits-web.git` this repository
|
||||||
* `cd kredits-web`
|
* `cd kredits-web`
|
||||||
* `npm install`
|
* `npm install`
|
||||||
* `bower install`
|
|
||||||
|
|
||||||
## Running / Development
|
## Running / Development
|
||||||
|
|
||||||
* `ember serve`
|
|
||||||
|
* `ember serve` - by default kredits-web connects to the Ethreum Kovan network
|
||||||
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
* 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
|
### Code Generators
|
||||||
|
|
||||||
@@ -36,6 +38,12 @@ Make use of the many generators for code, try `ember help generate` for more det
|
|||||||
* `ember test`
|
* `ember test`
|
||||||
* `ember test --server`
|
* `ember test --server`
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
* `npm run lint:hbs`
|
||||||
|
* `npm run lint:js`
|
||||||
|
* `npm run lint:js -- --fix`
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
* `ember build` (development)
|
* `ember build` (development)
|
||||||
@@ -43,11 +51,66 @@ Make use of the many generators for code, try `ember help generate` for more det
|
|||||||
|
|
||||||
### Deploying
|
### Deploying
|
||||||
|
|
||||||
Specify what it takes to deploy your app.
|
_(You need collaborator permissions on the 5apps Deploy project.)_
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
You can run `kredits-web` on your machine, against a local, simulated Ethereum
|
||||||
|
network, provided e.g. by [ganache](http://truffleframework.com/ganache/).
|
||||||
|
|
||||||
|
[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.
|
||||||
|
|
||||||
|
These are the basic steps to get up and running:
|
||||||
|
|
||||||
|
#### 1. IPFS
|
||||||
|
|
||||||
|
Run a local IPFS deamon in offline mode.
|
||||||
|
|
||||||
|
* Make sure CORS headers are configured. See [IPFS](#ipfs) for more info.
|
||||||
|
* `ipfs daemon --offline`
|
||||||
|
|
||||||
|
#### 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
|
## Further Reading / Useful Links
|
||||||
|
|
||||||
* [ember.js](http://emberjs.com/)
|
* [ember.js](https://emberjs.com/)
|
||||||
* [ember-cli](https://ember-cli.com/)
|
* [ember-cli](https://ember-cli.com/)
|
||||||
* Development Browser Extensions
|
* Development Browser Extensions
|
||||||
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||||
|
|||||||
+2
-6
@@ -1,13 +1,9 @@
|
|||||||
import Ember from 'ember';
|
import Application from '@ember/application';
|
||||||
import Resolver from './resolver';
|
import Resolver from './resolver';
|
||||||
import loadInitializers from 'ember-load-initializers';
|
import loadInitializers from 'ember-load-initializers';
|
||||||
import config from './config/environment';
|
import config from './config/environment';
|
||||||
|
|
||||||
let App;
|
const App = Application.extend({
|
||||||
|
|
||||||
Ember.MODEL_FACTORY_INJECTIONS = true;
|
|
||||||
|
|
||||||
App = Ember.Application.extend({
|
|
||||||
modulePrefix: config.modulePrefix,
|
modulePrefix: config.modulePrefix,
|
||||||
podModulePrefix: config.podModulePrefix,
|
podModulePrefix: config.podModulePrefix,
|
||||||
Resolver
|
Resolver
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<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,68 +1,76 @@
|
|||||||
import Ember from 'ember';
|
import Component from '@ember/component';
|
||||||
|
import { and, notEmpty } from '@ember/object/computed';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { isPresent } from '@ember/utils';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Component.extend({
|
||||||
|
|
||||||
id: null,
|
kredits: service(),
|
||||||
realName: null,
|
|
||||||
address: null,
|
attributes: null,
|
||||||
ipfsHash: null,
|
|
||||||
isCore: true,
|
// 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',
|
||||||
|
'isValidName',
|
||||||
|
'isValidGithubUID'
|
||||||
|
),
|
||||||
|
|
||||||
inProgress: false,
|
inProgress: false,
|
||||||
|
|
||||||
isValidId: function() {
|
init () {
|
||||||
return Ember.isPresent(this.get('id'));
|
this._super(...arguments);
|
||||||
}.property('id'),
|
this.setDefaultAttributes();
|
||||||
|
this.reset();
|
||||||
|
},
|
||||||
|
|
||||||
isValidRealName: function() {
|
setDefaultAttributes () {
|
||||||
return Ember.isPresent(this.get('realName'));
|
if (isPresent(this.attributes)) { return; }
|
||||||
}.property('realName'),
|
|
||||||
|
|
||||||
isValidAddress: function() {
|
this.set('attributes', {
|
||||||
return this.get('kredits.web3Instance').isAddress(this.get('address'));
|
account: null,
|
||||||
}.property('address'),
|
name: null,
|
||||||
|
kind: 'person',
|
||||||
isValid: function() {
|
url: null,
|
||||||
return this.get('isValidId') && this.get('isValidRealName') && this.get('isValidAddress');
|
github_username: null,
|
||||||
}.property('isValidAddress', 'isValidId', 'isValidRealName'),
|
github_uid: null,
|
||||||
|
gitea_username: null,
|
||||||
|
wiki_username: null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
reset: function() {
|
reset: function() {
|
||||||
this.setProperties({
|
this.setProperties(this.attributes);
|
||||||
id: null,
|
|
||||||
realName: null,
|
|
||||||
address: null,
|
|
||||||
ipfsHash: null,
|
|
||||||
isCore: true,
|
|
||||||
inProgress: false
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
save() {
|
submit () {
|
||||||
if (!this.get('contractInteractionEnabled')) {
|
if (!this.isValid) {
|
||||||
alert('Only core team members can add new contributors. Please ask someone to set you up.');
|
alert('Invalid data. Please review and try again.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.get('isValid')) {
|
const attributes = Object.keys(this.attributes);
|
||||||
this.set('inProgress', true);
|
const contributor = this.getProperties(attributes);
|
||||||
|
|
||||||
this.get('kredits').addContributor(
|
this.set('inProgress', true);
|
||||||
this.get('address'),
|
|
||||||
this.get('realName'),
|
this.save(contributor).then(() => {
|
||||||
'', // TODO: this.get('ipfsHash')
|
this.reset();
|
||||||
this.get('isCore'),
|
}).catch(err => {
|
||||||
this.get('id')
|
console.warn(err);
|
||||||
).then(contributor => {
|
window.alert('Something went wrong. Please check the browser console.');
|
||||||
this.reset();
|
}).finally(() => {
|
||||||
this.get('contributors').pushObject(contributor);
|
this.set('inProgress', false);
|
||||||
window.scroll(0,0);
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert('Invalid data. Please review and try again.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,50 @@
|
|||||||
<form {{action "save" on="submit"}}>
|
<form {{action "submit" on="submit"}}>
|
||||||
<p>
|
<p>
|
||||||
{{input name="id"
|
<label for="c-account">Ethereum account</label>
|
||||||
type="text"
|
{{input name="account" id="c-account"
|
||||||
placeholder="GitHub UID (123)"
|
type="text" value=account
|
||||||
value=id
|
|
||||||
class=(if isValidId 'valid' '')}}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{input name="realname"
|
|
||||||
type="text"
|
|
||||||
placeholder="Carl Sagan"
|
|
||||||
value=realName
|
|
||||||
class=(if isValidRealName 'valid' '')}}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{input name="address"
|
|
||||||
type="text"
|
|
||||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||||
value=address
|
class=(if isValidAccount "valid" "")}}
|
||||||
class=(if isValidAddress 'valid' '')}}
|
</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>
|
||||||
|
</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"}}
|
||||||
|
</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"}}
|
||||||
|
</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"}}
|
||||||
|
</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"}}
|
||||||
|
</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"}}
|
||||||
</p>
|
</p>
|
||||||
<p class="actions">
|
<p class="actions">
|
||||||
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
|
{{input type="submit" disabled=inProgress
|
||||||
|
value=(if inProgress "Processing" "Save")}}
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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'),
|
||||||
|
isValidAmount: computed('amount', function() {
|
||||||
|
return parseInt(this.amount, 10) > 0;
|
||||||
|
}),
|
||||||
|
isValidDescription: notEmpty('description'),
|
||||||
|
isValidUrl: notEmpty('url'),
|
||||||
|
isValid: and('isValidContributor',
|
||||||
|
'isValidAmount',
|
||||||
|
'isValidDescription'),
|
||||||
|
|
||||||
|
init () {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
// Default attributes used by reset
|
||||||
|
this.set('attributes', {
|
||||||
|
contributorId: null,
|
||||||
|
kind: 'community',
|
||||||
|
amount: null,
|
||||||
|
description: null,
|
||||||
|
url: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.setProperties(this.attributes);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
submit() {
|
||||||
|
if (!this.isValid) {
|
||||||
|
alert('Invalid data. Please review and try again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attributes = Object.keys(this.attributes);
|
||||||
|
let proposal = this.getProperties(attributes);
|
||||||
|
let saved = this.save(proposal);
|
||||||
|
|
||||||
|
// The promise handles inProgress
|
||||||
|
this.set('inProgress', saved);
|
||||||
|
|
||||||
|
saved.then(() => {
|
||||||
|
this.reset();
|
||||||
|
window.scroll(0,0);
|
||||||
|
window.alert('Proposal added.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<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.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>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input type="text"
|
||||||
|
placeholder="100"
|
||||||
|
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=(is-pending inProgress)
|
||||||
|
value=(if (is-pending inProgress) "Processing" "Save")}}
|
||||||
|
{{#link-to "index"}}Back{{/link-to}}
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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'
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="chart">
|
||||||
|
{{ember-chart type="doughnut"
|
||||||
|
data=chartData
|
||||||
|
options=chartOptions
|
||||||
|
width=200
|
||||||
|
height=200}}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
|
||||||
|
tagName: 'div',
|
||||||
|
classNames: ['contributions'],
|
||||||
|
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
{{#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>
|
||||||
@@ -1,8 +1,20 @@
|
|||||||
import Ember from 'ember';
|
import Component from '@ember/component';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Component.extend({
|
||||||
|
|
||||||
|
router: service(),
|
||||||
|
|
||||||
tagName: 'table',
|
tagName: 'table',
|
||||||
classNames: 'contributor-list'
|
classNames: 'contributor-list',
|
||||||
|
selectedContributor: null,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
openContributorDetails(contributor) {
|
||||||
|
this.router.transitionTo('dashboard.contributors.show', contributor);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{#each contributors as |contributor|}}
|
{{#each contributorList as |c|}}
|
||||||
<tr class="{{if contributor.isCurrentUser 'current-user'}}">
|
<tr role="button" class={{if (is-current-user c.contributor) "current-user"}} {{action "openContributorDetails" c.contributor}}>
|
||||||
<td class="person">
|
<td class="person">
|
||||||
<img class="avatar" src={{contributor.avatarURL}}>
|
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
|
||||||
{{contributor.github_username}}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="kredits">
|
<td class="kredits">
|
||||||
<span class="amount">{{contributor.kredits}}</span>
|
<span class="amount">
|
||||||
|
{{#if showUnconfirmedKredits}}
|
||||||
|
{{c.amountTotal}}
|
||||||
|
{{else}}
|
||||||
|
{{c.amountConfirmed}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
<span class="symbol">₭S</span>
|
<span class="symbol">₭S</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<a href={{account.url}} target="_blank" rel="noopener" title="{{account.username}} on {{account.site}}">
|
||||||
|
<span class="site">{{account.site}}</span>
|
||||||
|
{{component iconComponentName}}
|
||||||
|
</a>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: ""
|
||||||
|
});
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.0 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: ""
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: ""
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 703 B |
@@ -0,0 +1,5 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: ""
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1,6 +1,6 @@
|
|||||||
import Ember from 'ember';
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Component.extend({
|
||||||
|
|
||||||
classNames: ['loading-spinner']
|
classNames: ['loading-spinner']
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
<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"
|
version="1.1" x="0px" y="0px" viewBox="0 0 132 100"
|
||||||
enable-background="new 0 0 100 100" xml:space="preserve">
|
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
|
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
|
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">
|
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>
|
</svg>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Loading data from Ethereum...
|
Loading data...
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Ember from 'ember';
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Component.extend({
|
||||||
|
|
||||||
tagName: 'ul',
|
tagName: 'ul',
|
||||||
classNames: ['proposal-list'],
|
classNames: ['proposal-list'],
|
||||||
@@ -8,8 +8,8 @@ export default Ember.Component.extend({
|
|||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
confirm(proposalId) {
|
confirm(proposalId) {
|
||||||
if (this.get('contractInteractionEnabled')) {
|
if (this.contractInteractionEnabled) {
|
||||||
this.sendAction('confirmAction', proposalId);
|
this.confirmProposal(proposalId);
|
||||||
} else {
|
} else {
|
||||||
window.alert('Only members can vote on proposals. Please ask someone to set you up.');
|
window.alert('Only members can vote on proposals. Please ask someone to set you up.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
{{#each proposals as |proposal|}}
|
{{#each proposals as |proposal|}}
|
||||||
<li>
|
<li data-proposal-id={{proposal.id}} class={{if proposal.isExecuted "confirmed" "unconfirmed"}}>
|
||||||
<span class="id">#{{proposal.id}}</span>
|
<p class="meta">
|
||||||
Issue{{#if proposal.executed}}d{{/if}}
|
<span class="category {{proposal.kind}}">♥ ({{proposal.kind}})</span>
|
||||||
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
|
<span class="recipient">{{proposal.contributor.name}}:</span>
|
||||||
to <span class="recipient" title="{{proposal.recipientAddress}}">{{proposal.recipientName}}</span>
|
</p>
|
||||||
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
|
<p class="kredits-amount">
|
||||||
|
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
|
||||||
{{#unless proposal.executed}}<button {{action "confirm" proposal.id}}>+1</button>{{/unless}}
|
</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>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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]}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import Ember from 'ember';
|
import Controller from '@ember/controller';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Controller.extend({
|
||||||
|
kredits: service(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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'))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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'))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
|
||||||
|
export default Controller.extend({
|
||||||
|
|
||||||
|
roleName: computed('model.isCore', 'totalKreditsEarned', function() {
|
||||||
|
if (this.model.isCore) return 'Core Contributor';
|
||||||
|
if (this.model.totalKreditsEarned <= 5000) return 'Newcomer';
|
||||||
|
return 'Contributor';
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
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')).github_username);
|
|
||||||
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')).github_username);
|
|
||||||
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'),
|
|
||||||
|
|
||||||
contributorsSorting: ['kredits:desc'],
|
|
||||||
contributorsSorted: Ember.computed.sort('model.contributors', '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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { alias, filterBy } 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'),
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import Helper from '@ember/component/helper';
|
||||||
|
|
||||||
|
export default Helper.extend({
|
||||||
|
compute([string]) {
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
+9
-3
@@ -3,16 +3,22 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<title>Kredits</title>
|
<title>Kosmos Kredits</title>
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
|
||||||
{{content-for "head"}}
|
{{content-for "head"}}
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
|
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
|
||||||
<link rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
||||||
|
|
||||||
{{content-for "head-footer"}}
|
{{content-for "head-footer"}}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
window.windowLoadComplete = true;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{content-for "body"}}
|
{{content-for "body"}}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import EmberObject from '@ember/object';
|
||||||
|
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||||
|
|
||||||
|
export default EmberObject.extend({
|
||||||
|
|
||||||
|
// Contract
|
||||||
|
id: null,
|
||||||
|
contributorId: null,
|
||||||
|
amount: null,
|
||||||
|
confirmedAt: bignumber('confirmedAtBlock', 'toNumber'),
|
||||||
|
vetoed: null,
|
||||||
|
ipfsHash: null,
|
||||||
|
|
||||||
|
creatorAccount: null,
|
||||||
|
|
||||||
|
// IPFS
|
||||||
|
kind: null,
|
||||||
|
description: null,
|
||||||
|
details: null,
|
||||||
|
url: null,
|
||||||
|
ipfsData: '',
|
||||||
|
|
||||||
|
init () {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.set('details', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
+18
-10
@@ -1,16 +1,24 @@
|
|||||||
import Ember from 'ember';
|
import EmberObject from '@ember/object';
|
||||||
|
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||||
|
import kreditsValue from 'kredits-web/utils/cps/kredits';
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
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,
|
||||||
|
|
||||||
address: null,
|
// IPFS
|
||||||
|
kind: null,
|
||||||
|
name: null,
|
||||||
|
url: null,
|
||||||
github_username: null,
|
github_username: null,
|
||||||
github_uid: null,
|
github_uid: null,
|
||||||
ipfsHash: null,
|
wiki_username: null,
|
||||||
kredits: null,
|
ipfsData: ''
|
||||||
isCurrentUser: false,
|
|
||||||
|
|
||||||
avatarURL: function() {
|
|
||||||
return `https\:\/\/avatars2.githubusercontent.com/u/${this.get('github_uid')}?v=3&s=128`;
|
|
||||||
}.property('github_uid')
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
+26
-10
@@ -1,16 +1,32 @@
|
|||||||
import Ember from 'ember';
|
import EmberObject from '@ember/object';
|
||||||
|
import { alias } from '@ember/object/computed';
|
||||||
|
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
export default EmberObject.extend({
|
||||||
|
|
||||||
id: null,
|
// Contract
|
||||||
creatorAddress: null,
|
id: bignumber('idRaw', 'toString'),
|
||||||
recipientAddress: null,
|
creatorAccount: null,
|
||||||
recipientName: null,
|
contributorId: bignumber('contributorIdRaw', 'toString'),
|
||||||
votesCount: null,
|
amount: bignumber('amountRaw', 'toNumber'),
|
||||||
votesNeeded: null,
|
votesCount: bignumber('votesCountRaw', 'toNumber'),
|
||||||
amount: null,
|
votesNeeded: bignumber('votesNeededRaw', 'toNumber'),
|
||||||
executed: null,
|
executed: null,
|
||||||
|
ipfsHash: null,
|
||||||
|
|
||||||
|
// Shortcuts
|
||||||
|
isExecuted: alias('executed'),
|
||||||
|
|
||||||
|
// IPFS
|
||||||
|
kind: null,
|
||||||
|
description: null,
|
||||||
|
details: null,
|
||||||
url: null,
|
url: null,
|
||||||
ipfsHash: null
|
ipfsData: '',
|
||||||
|
|
||||||
|
init () {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.set('details', {});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
+17
-3
@@ -1,13 +1,27 @@
|
|||||||
import Ember from 'ember';
|
import EmberRouter from '@ember/routing/router';
|
||||||
import config from './config/environment';
|
import config from './config/environment';
|
||||||
|
|
||||||
const Router = Ember.Router.extend({
|
const Router = EmberRouter.extend({
|
||||||
location: config.locationType,
|
location: config.locationType,
|
||||||
rootURL: config.rootURL
|
rootURL: config.rootURL
|
||||||
});
|
});
|
||||||
|
|
||||||
Router.map(function() {
|
Router.map(function() {
|
||||||
this.route('spinner');
|
this.route('dashboard', function() {
|
||||||
|
this.route('contributors', function() {
|
||||||
|
this.route('show', { path: ':id' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
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;
|
export default Router;
|
||||||
|
|||||||
@@ -1,4 +1,31 @@
|
|||||||
import Ember from 'ember';
|
import { inject as service } from '@ember/service';
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
+4
-13
@@ -1,18 +1,9 @@
|
|||||||
import Ember from 'ember';
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
export default Route.extend({
|
||||||
|
|
||||||
kredits: Ember.inject.service(),
|
redirect () {
|
||||||
|
this.transitionTo('dashboard');
|
||||||
model() {
|
|
||||||
let kredits = this.get('kredits');
|
|
||||||
|
|
||||||
return Ember.RSVP.hash({
|
|
||||||
contributors: kredits.getContributors(),
|
|
||||||
totalSupply: kredits.getValueFromContract('totalSupply'),
|
|
||||||
contributorsCount: kredits.getValueFromContract('contributorsCount'),
|
|
||||||
proposals: kredits.getProposals()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import Ember from 'ember';
|
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
|
||||||
});
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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 => {
|
|
||||||
return res[0].hash;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getFile(hash) {
|
|
||||||
return this.get('ipfs').cat(hash, { buffer: true }).then(res => {
|
|
||||||
return res.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
+375
-139
@@ -1,164 +1,400 @@
|
|||||||
import Ember from 'ember';
|
import ethers from 'npm:ethers';
|
||||||
import Web3 from 'npm:web3';
|
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 config from 'kredits-web/config/environment';
|
import config from 'kredits-web/config/environment';
|
||||||
import Contributor from 'kredits-web/models/contributor';
|
import Contributor from 'kredits-web/models/contributor'
|
||||||
import Proposal from 'kredits-web/models/proposal';
|
import Proposal from 'kredits-web/models/proposal'
|
||||||
import kreditsContracts from 'npm:kredits-contracts';
|
import Contribution from 'kredits-web/models/contribution'
|
||||||
|
|
||||||
export default Ember.Service.extend({
|
export default Service.extend({
|
||||||
|
|
||||||
ipfs: Ember.inject.service(),
|
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,
|
||||||
|
|
||||||
web3Instance: null,
|
currentUserIsContributor: notEmpty('currentUser'),
|
||||||
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
|
currentUserIsCore: alias('currentUser.isCore'),
|
||||||
|
hasAccounts: notEmpty('currentUserAccounts'),
|
||||||
|
|
||||||
web3: function() {
|
accountNeedsUnlock: computed('currentUserAccounts', function() {
|
||||||
if (this.get('web3Instance')) {
|
return this.currentUserAccounts && isEmpty(this.currentUserAccounts);
|
||||||
return this.get('web3Instance');
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
let web3Instance;
|
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||||
|
return this.contributions.filter(contribution => {
|
||||||
if (typeof window.web3 !== 'undefined') {
|
return contribution.confirmedAt > this.currentBlock;
|
||||||
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 provider = new Web3.providers.HttpProvider(config.web3ProviderUrl);
|
|
||||||
web3Instance = new Web3(provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set('web3Instance', web3Instance);
|
|
||||||
window.web3 = web3Instance;
|
|
||||||
|
|
||||||
return web3Instance;
|
|
||||||
}.property('web3Instance'),
|
|
||||||
|
|
||||||
currentUserAccounts: function() {
|
|
||||||
return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
|
|
||||||
}.property('web3', 'web3Provided'),
|
|
||||||
|
|
||||||
kreditsContract: function() {
|
|
||||||
if (this.get('kreditsContractInstance')) {
|
|
||||||
return this.get('kreditsContractInstance');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(config.ethereumChain);
|
|
||||||
let contract = kreditsContracts(this.get('web3'), config.ethereumChain)['Kredits'];
|
|
||||||
|
|
||||||
this.set('kreditsContractInstance', contract);
|
|
||||||
return contract;
|
|
||||||
}.property('web3'),
|
|
||||||
|
|
||||||
getValueFromContract(contractMethod, ...args) {
|
|
||||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
|
||||||
this.get('kreditsContract')[contractMethod](...args, (err, data) => {
|
|
||||||
if (err) { reject(err); }
|
|
||||||
resolve(data);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
contributionsConfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||||
|
return this.contributions
|
||||||
|
.filterBy('vetoed', false)
|
||||||
|
.filter(contribution => {
|
||||||
|
return contribution.confirmedAt <= this.currentBlock;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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 EmberObject.create({
|
||||||
|
contributor: contributor,
|
||||||
|
amountUnconfirmed: amountUnconfirmed,
|
||||||
|
amountConfirmed: contributor.totalKreditsEarned,
|
||||||
|
amountTotal: contributor.totalKreditsEarned + amountUnconfirmed
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
contributorsWithOnlyConfirmed.forEach(c => {
|
||||||
|
kreditsByContributor.push(EmberObject.create({
|
||||||
|
contributor: c,
|
||||||
|
amountUnconfirmed: 0,
|
||||||
|
amountConfirmed: c.totalKreditsEarned,
|
||||||
|
amountTotal: c.totalKreditsEarned
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
|
||||||
|
return kreditsByContributor;
|
||||||
|
}),
|
||||||
|
|
||||||
|
init () {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.set('contributors', []);
|
||||||
|
this.set('proposals', []);
|
||||||
|
this.set('contributions', []);
|
||||||
},
|
},
|
||||||
|
|
||||||
getContributorData(i) {
|
// This is called in the application route's beforeModel(). So it is
|
||||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
// initialized before everything else, and we can rely on the ethProvider and
|
||||||
this.getValueFromContract('contributorAddresses', i).then(address => {
|
// the potential currentUserAccounts to be available
|
||||||
this.getValueFromContract('contributors', address).then(person => {
|
getEthProvider () {
|
||||||
this.getValueFromContract('balanceOf', address).then(balance => {
|
let ethProvider;
|
||||||
let contributor = Contributor.create({
|
|
||||||
address: address,
|
return new RSVP.Promise(async (resolve) => {
|
||||||
github_username: person[1],
|
function instantiateWithoutAccount () {
|
||||||
github_uid: person[0],
|
console.debug('[kredits] Creating new instance from npm module class');
|
||||||
ipfsHash: person[3],
|
console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`);
|
||||||
kredits: balance.toNumber(),
|
ethProvider = new ethers.providers.JsonRpcProvider(config.web3ProviderUrl);
|
||||||
isCurrentUser: this.get('currentUserAccounts').includes(address)
|
resolve({
|
||||||
});
|
ethProvider: ethProvider,
|
||||||
Ember.Logger.debug('[kredits] contributor', contributor);
|
ethSigner: null
|
||||||
resolve(contributor);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch(err => reject(err));
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
getContributors() {
|
|
||||||
return this.getValueFromContract('contributorsCount').then(contributorsCount => {
|
|
||||||
let contributors = [];
|
|
||||||
|
|
||||||
for(var i = 0; i < contributorsCount.toNumber(); i++) {
|
|
||||||
contributors.push(this.getContributorData(i));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ember.RSVP.all(contributors);
|
if (window.ethereum) {
|
||||||
});
|
try {
|
||||||
},
|
// Request account access if needed
|
||||||
|
await window.ethereum.enable();
|
||||||
getProposalData(i) {
|
// Acccounts now exposed
|
||||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
instantiateWithAccount(window.ethereum, this);
|
||||||
this.getValueFromContract('proposals', i).then(p => {
|
} catch (error) {
|
||||||
let proposal = Proposal.create({
|
instantiateWithoutAccount();
|
||||||
id : i,
|
}
|
||||||
creatorAddress : p[0],
|
}
|
||||||
recipientAddress : p[1],
|
// Legacy dapp browsers...
|
||||||
votesCount : p[2].toNumber(),
|
else if (window.web3) {
|
||||||
votesNeeded : p[3].toNumber(),
|
instantiateWithAccount(window.web3.currentProvider, this);
|
||||||
amount : p[4].toNumber(),
|
}
|
||||||
executed : p[5],
|
// Non-dapp browsers...
|
||||||
url : p[6],
|
else {
|
||||||
ipfsHash : p[7]
|
instantiateWithoutAccount();
|
||||||
});
|
|
||||||
Ember.Logger.debug('[kredits] proposal', proposal);
|
|
||||||
resolve(proposal);
|
|
||||||
}).catch(err => reject(err));
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
getProposals() {
|
|
||||||
return this.getValueFromContract('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) {
|
setup () {
|
||||||
Ember.Logger.debug('[kredits] vote for', proposalId);
|
return this.getEthProvider().then((providerAndSigner) => {
|
||||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
let kredits = new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
|
||||||
this.get('kreditsContract').vote(proposalId, (err, data) => {
|
addresses: { Kernel: config.kreditsKernelAddress },
|
||||||
if (err) { reject(err); }
|
apm: config.kreditsApmDomain,
|
||||||
Ember.Logger.debug('[kredits] vote response', data);
|
ipfsConfig: config.ipfs
|
||||||
resolve(data);
|
|
||||||
});
|
});
|
||||||
});
|
return kredits
|
||||||
},
|
.init()
|
||||||
|
.then(async (kredits) => {
|
||||||
|
this.set('kredits', kredits);
|
||||||
|
this.set('currentBlock', await kredits.provider.getBlockNumber());
|
||||||
|
|
||||||
addContributor(address, name, ipfsHash, isCore, id) {
|
if (this.currentUserAccounts && this.currentUserAccounts.length > 0) {
|
||||||
Ember.Logger.debug('[kredits] add contributor', name, address);
|
this.getCurrentUser.then((contributorData) => {
|
||||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
this.set('currentUser', contributorData);
|
||||||
this.get('kreditsContract').addContributor(address, name, ipfsHash, isCore, id, (err, data) => {
|
});
|
||||||
if (err) { reject(err); }
|
}
|
||||||
Ember.Logger.debug('[kredits] add contributor response', data);
|
|
||||||
let contributor = Contributor.create({
|
return kredits;
|
||||||
address: address,
|
|
||||||
github_username: name,
|
|
||||||
github_uid: id,
|
|
||||||
ipfsHash: ipfsHash,
|
|
||||||
kredits: 0,
|
|
||||||
isCurrentUser: this.get('currentUserAccounts').includes(address)
|
|
||||||
});
|
});
|
||||||
resolve(contributor);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
logKreditsContract: function() {
|
totalSupply: computed(function() {
|
||||||
Ember.Logger.debug('[kredits] kreditsContract', this.get('kreditsContract'));
|
return this.kredits.Token.functions.totalSupply().then(total => {
|
||||||
}.on('init')
|
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();
|
||||||
|
} else {
|
||||||
|
return this.kredits.Contributor.getById(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
this.kredits.Contribution
|
||||||
|
.on('ContributionVetoed', this.handleContributionVetoed.bind(this))
|
||||||
|
|
||||||
|
this.kredits.Proposal
|
||||||
|
.on('ProposalCreated', this.handleProposalCreated.bind(this))
|
||||||
|
.on('ProposalVoted', this.handleProposalVoted.bind(this))
|
||||||
|
.on('ProposalExecuted', this.handleProposalExecuted.bind(this));
|
||||||
|
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: We may want to reload that proposal to show the voter as voted
|
||||||
|
handleProposalVoted (proposalId, voterId, totalVotes) {
|
||||||
|
let proposal = this.findProposalById(proposalId);
|
||||||
|
|
||||||
|
if (proposal) {
|
||||||
|
proposal.set('votesCount', totalVotes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleProposalExecuted (proposalId, contributorId, amount) {
|
||||||
|
let proposal = this.findProposalById(proposalId);
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTransfer (from, to, value) {
|
||||||
|
value = value.toNumber();
|
||||||
|
|
||||||
|
this.contributors
|
||||||
|
.findBy('address', from)
|
||||||
|
.decrementProperty('balance', value);
|
||||||
|
|
||||||
|
this.contributors
|
||||||
|
.findBy('address', to)
|
||||||
|
.incrementProperty('balance', value);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
$blue: #68d7fb;
|
||||||
|
$purple: #8f68fb;
|
||||||
|
$pink: #e068fb;
|
||||||
|
$green: #97fb68;
|
||||||
|
$yellow: #fbe468;
|
||||||
|
$red: #fb6868;
|
||||||
|
|
||||||
|
$primary-color: $blue;
|
||||||
|
|
||||||
|
$body-text-color: #fff;
|
||||||
|
|
||||||
|
#topbar {
|
||||||
|
background-color: rgba(255, 0, 255, .2);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
section#add-contributor,
|
||||||
|
section#add-contribution,
|
||||||
|
section#add-proposal {
|
||||||
|
|
||||||
|
form {
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
&.actions {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
a {
|
||||||
|
color: $primary-color;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text], select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
|
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 {
|
||||||
|
color: rgba(238, 238, 238, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: rgba(22, 21, 40, 0.6);
|
||||||
|
background-image:
|
||||||
|
linear-gradient(45deg, transparent 50%, gray 50%),
|
||||||
|
linear-gradient(135deg, gray 50%, transparent 50%);
|
||||||
|
background-position:
|
||||||
|
calc(100% - 1.5rem) calc(1rem + 0.5rem),
|
||||||
|
calc(100% - 1rem) calc(1rem + 0.5rem);
|
||||||
|
background-size:
|
||||||
|
0.5rem 0.5rem,
|
||||||
|
0.5rem 0.5rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
&:invalid {
|
||||||
|
color: rgba(238, 238, 238, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=submit] {
|
||||||
|
padding: 0.6rem 2rem;
|
||||||
|
&:disabled {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkbox {
|
||||||
|
line-height: 3.2rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.8rem;
|
||||||
|
height: 3.2rem;
|
||||||
|
width: 3.2rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
background-color: rgba(22, 21, 40, 0.3);
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
content: '✓';
|
||||||
|
color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox]:checked + label.checkbox {
|
||||||
|
&::before {
|
||||||
|
background-color: rgba(22, 21, 40, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+80
-22
@@ -1,31 +1,89 @@
|
|||||||
section {
|
main {
|
||||||
@include outer-container;
|
padding: 1rem 2rem;
|
||||||
margin-bottom: 8rem;
|
|
||||||
&:first-of-type {
|
@include media-max(small) {
|
||||||
margin-top: 6rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media($mobile) {
|
&#dashboard {
|
||||||
margin-bottom: 5rem;
|
width: 100%;
|
||||||
&:first-of-type {
|
display: grid;
|
||||||
margin-top: 2rem;
|
grid-row-gap: 2rem;
|
||||||
}
|
grid-template-areas:
|
||||||
|
"stats"
|
||||||
|
"contributions";
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
&.center-column {
|
||||||
@include span-columns(12);
|
display: flex;
|
||||||
padding-bottom: 3rem;
|
flex-direction: column;
|
||||||
text-align: center;
|
align-items: center;
|
||||||
// background-color: purple;
|
|
||||||
|
|
||||||
@include media($mobile) {
|
section {
|
||||||
@include span-columns(10);
|
width: 600px;
|
||||||
@include shift(1);
|
max-width: 100%;
|
||||||
padding-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content {
|
header {
|
||||||
@include span-columns(12);
|
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;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-max(small) {
|
||||||
|
margin-bottom: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
|
||||||
|
&.with-nav {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 5rem;
|
||||||
|
grid-template-areas:
|
||||||
|
"title" "actions";
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
$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."
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
-35
@@ -2,6 +2,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
*:hover, *:active, *:focus {
|
*:hover, *:active, *:focus {
|
||||||
@@ -10,22 +11,19 @@
|
|||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@import "bourbon";
|
@import "mediaqueries";
|
||||||
@import "settings/neat";
|
|
||||||
@import "neat";
|
|
||||||
@import "settings/breakpoints";
|
|
||||||
@import "layout";
|
@import "layout";
|
||||||
|
@import "colors";
|
||||||
|
|
||||||
$font-family-sans: 'Open Sans', sans-serif;
|
$font-family-sans: 'Open Sans', sans-serif;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-image: url('/img/bg.jpg');
|
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-repeat: none;
|
background-repeat: none;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
font-family: $font-family-sans;
|
color: $body-text-color;
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 900px) {
|
@media screen and (min-width: 900px) {
|
||||||
@@ -42,48 +40,69 @@ h1, h2, h3, h4, h5, input, button {
|
|||||||
|
|
||||||
section {
|
section {
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 2.8rem;
|
font-size: 1.4rem;
|
||||||
color: lightblue;
|
color: $primary-color;
|
||||||
@include media($mobile) {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&#contributors {
|
&#people {
|
||||||
.content {
|
.content {
|
||||||
p.stats {
|
p.stats {
|
||||||
padding-top: 3rem;
|
margin-bottom: 1rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: lightblue;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
span.number {
|
span.number {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@include media($mobile) {
|
}
|
||||||
padding-top: 2rem;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&#contributions-by-type {
|
||||||
|
.chart {
|
||||||
|
width: 50%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
@include media-max(small) {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&#proposals-open, &#proposals-closed {
|
||||||
|
.actions {
|
||||||
|
padding-top: 3rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: $primary-color;
|
||||||
|
text-align: center;
|
||||||
|
@include media-max(small) {
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button, input[type=submit] {
|
@media (min-width: 550px) {
|
||||||
display: inline-block;
|
section {
|
||||||
border: 1px solid rgba(22, 21, 40, 1);
|
h2 {
|
||||||
background-color: rgba(22, 21, 40, 0.6);
|
font-size: 2rem;
|
||||||
color: lightblue;
|
}
|
||||||
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 "components/loading-spinner";
|
@import "buttons";
|
||||||
|
@import "forms";
|
||||||
|
|
||||||
|
@import "components/contribution-list";
|
||||||
@import "components/contributor-list";
|
@import "components/contributor-list";
|
||||||
@import "components/add-contributor";
|
@import "components/contributor-profile";
|
||||||
|
@import "components/external-account-link";
|
||||||
|
@import "components/loading-spinner";
|
||||||
@import "components/proposal-list";
|
@import "components/proposal-list";
|
||||||
|
@import "components/topbar";
|
||||||
|
@import "components/user-avatar";
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
section#add-contributor {
|
|
||||||
|
|
||||||
form {
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
&.actions {
|
|
||||||
padding-top: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 1rem;
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
|
||||||
background-color: rgba(22, 21, 40, 0.3);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
&:focus, &.valid {
|
|
||||||
background-color: rgba(22, 21, 40, 0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=submit] {
|
|
||||||
padding: 0.6rem 2rem;
|
|
||||||
&:disabled {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
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;
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
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;
|
||||||
|
&.community { color: $red; }
|
||||||
|
&.dev { color: $pink; }
|
||||||
|
&.design { color: $yellow; }
|
||||||
|
&.docs { color: $green; }
|
||||||
|
&.ops { color: $purple; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
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,45 +1,89 @@
|
|||||||
table.contributor-list {
|
table.contributor-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
border-top: 1px solid rgba(255,255,255,0.2);
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
}
|
}
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
|
||||||
|
&.current-user {
|
||||||
|
background-color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.metadata {
|
||||||
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
&:not(.visible) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $primary-color;
|
||||||
|
&:hover, &:active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline;
|
||||||
|
&+li {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
height: auto;
|
||||||
|
visibility: visible;
|
||||||
|
ul {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0 1.2rem;
|
padding: 0 1.2rem;
|
||||||
line-height: 4.2rem;
|
line-height: 3.6rem;
|
||||||
|
|
||||||
background-color: rgba(255,255,255,0.1);
|
|
||||||
&.person {
|
&.person {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 1.4rem;
|
font-size: 1.2rem;
|
||||||
|
|
||||||
img.avatar {
|
img.avatar {
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 0.2rem;
|
margin-right: 0.2rem;
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.kredits {
|
&.kredits {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
.amount {
|
.amount {
|
||||||
font-size: 1.4rem;
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.symbol {
|
.symbol {
|
||||||
font-size: 1rem;
|
font-size: 0.8rem;
|
||||||
padding-left: 0.2rem;
|
padding-left: 0.2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.current-user {
|
pre {
|
||||||
td {
|
line-height: 1rem;
|
||||||
background-color: rgba(255,255,255,0.2);
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.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;
|
margin-top: 12rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: lightblue;
|
color: $primary-color;
|
||||||
|
|
||||||
@include media($mobile) {
|
@include media-max(small) {
|
||||||
margin-top: 6rem;
|
margin-top: 6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
#path-comet {
|
#path-comet {
|
||||||
fill: lightblue;
|
fill: $primary-color;
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
|
|
||||||
animation-name: pulse;
|
animation-name: pulse;
|
||||||
|
|||||||
@@ -4,44 +4,111 @@ ul.proposal-list {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
// display: block;
|
display: grid;
|
||||||
padding: 0 1.2rem;
|
grid-template-columns: auto 5rem;
|
||||||
line-height: 4.2rem;
|
grid-row-gap: 0.5rem;
|
||||||
|
padding: 1rem 1.2rem;
|
||||||
background-color: rgba(255,255,255,0.1);
|
background-color: rgba(255,255,255,0.1);
|
||||||
font-size: 1.4rem;
|
font-size: 1.2rem;
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
border-top: 1px solid rgba(255,255,255,0.2);
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.id {
|
p {
|
||||||
color: lightblue;
|
align-self: center;
|
||||||
padding-right: 0.2rem;
|
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;
|
||||||
|
&.community { color: $red; }
|
||||||
|
&.dev { color: $pink; }
|
||||||
|
&.design { color: $yellow; }
|
||||||
|
&.docs { color: $green; }
|
||||||
|
&.ops { color: $purple; }
|
||||||
|
}
|
||||||
|
|
||||||
.amount {
|
.amount {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.symbol {
|
.symbol {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding-left: 0.2rem;
|
padding-left: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recipient {
|
.recipient {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.votes {
|
.votes {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: lightblue;
|
color: $primary-color;
|
||||||
padding-left: 1rem;
|
margin-right: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
$mobile: new-breakpoint(max-width 600px);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// $visual-grid: true;
|
|
||||||
// $visual-grid-color: red;
|
|
||||||
// $visual-grid-index: front;
|
|
||||||
// $visual-grid-opacity: 0.5;
|
|
||||||
$max-width: em(500, 12);
|
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
<main>
|
<header id="topbar">
|
||||||
{{outlet}}
|
<h1>{{link-to "Kosmos Kredits" "dashboard"}}</h1>
|
||||||
</main>
|
|
||||||
|
<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}}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<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>
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<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">{{model.contributorsCount}}</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}}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<section id="proposals-closed">
|
|
||||||
<header>
|
|
||||||
<h2>Closed Proposals</h2>
|
|
||||||
</header>
|
|
||||||
<div class="content">
|
|
||||||
{{proposal-list proposals=proposalsClosedSorted
|
|
||||||
confirmAction="confirmProposal"}}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="add-contributor">
|
|
||||||
<header>
|
|
||||||
<h2>Add Contributor</h2>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
{{add-contributor kredits=kredits
|
|
||||||
contributors=model.contributors
|
|
||||||
contractInteractionEnabled=contractInteractionEnabled}}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<section id="add-proposal">
|
||||||
|
<header>
|
||||||
|
<h2>Add Proposal</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{{add-proposal contributors=minedContributors save=(action "save")}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{{loading-spinner}}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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]();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "kredits-web",
|
|
||||||
"dependencies": {
|
|
||||||
"ember": "~2.10.0",
|
|
||||||
"ember-cli-shims": "0.1.3",
|
|
||||||
"bourbon": "^4.2.7",
|
|
||||||
"neat": "^1.8.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+34
-10
@@ -1,10 +1,14 @@
|
|||||||
/* jshint node: true */
|
/* jshint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
module.exports = function(environment) {
|
module.exports = function(environment) {
|
||||||
var ENV = {
|
|
||||||
|
let ENV = {
|
||||||
modulePrefix: 'kredits-web',
|
modulePrefix: 'kredits-web',
|
||||||
environment: environment,
|
environment,
|
||||||
rootURL: '/',
|
rootURL: '/',
|
||||||
locationType: 'auto',
|
locationType: 'auto',
|
||||||
|
|
||||||
EmberENV: {
|
EmberENV: {
|
||||||
FEATURES: {
|
FEATURES: {
|
||||||
// Here you can enable experimental features on an ember canary build
|
// Here you can enable experimental features on an ember canary build
|
||||||
@@ -22,6 +26,7 @@ module.exports = function(environment) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
browserify: {
|
browserify: {
|
||||||
|
tests: true,
|
||||||
transform: [
|
transform: [
|
||||||
["babelify", {
|
["babelify", {
|
||||||
presets: ["es2015"],
|
presets: ["es2015"],
|
||||||
@@ -30,12 +35,13 @@ module.exports = function(environment) {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
web3ProviderUrl: "https://parity.kosmos.org:8545",
|
web3ProviderUrl: 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9',
|
||||||
ethereumChain: "testnet",
|
|
||||||
ipfs: {
|
ipfs: {
|
||||||
host: 'localhost',
|
host: 'ipfs.kosmos.org',
|
||||||
port: '5001',
|
port: '5444',
|
||||||
protocol: 'http'
|
protocol: 'https',
|
||||||
|
gatewayUrl: 'https://ipfs.kosmos.org/ipfs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,7 +51,14 @@ module.exports = function(environment) {
|
|||||||
// ENV.APP.LOG_TRANSITIONS = true;
|
// ENV.APP.LOG_TRANSITIONS = true;
|
||||||
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
||||||
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||||
ENV.ethereumChain = 'dev';
|
ENV.web3ProviderUrl = 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9';
|
||||||
|
|
||||||
|
ENV.ipfs = {
|
||||||
|
host: 'localhost',
|
||||||
|
port: '5001',
|
||||||
|
protocol: 'http',
|
||||||
|
gatewayUrl: 'http://localhost:8080/ipfs'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment === 'test') {
|
if (environment === 'test') {
|
||||||
@@ -57,11 +70,22 @@ module.exports = function(environment) {
|
|||||||
ENV.APP.LOG_VIEW_LOOKUPS = false;
|
ENV.APP.LOG_VIEW_LOOKUPS = false;
|
||||||
|
|
||||||
ENV.APP.rootElement = '#ember-testing';
|
ENV.APP.rootElement = '#ember-testing';
|
||||||
|
ENV.APP.autoboot = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment === 'production') {
|
if (environment === 'production') {
|
||||||
ENV.ethereumChain = 'testnet';
|
// here you can enable a production-specific feature
|
||||||
ENV.web3ProviderUrl = 'https://ropsten.infura.io';
|
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;
|
return ENV;
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"jquery-integration": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'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
|
||||||
|
};
|
||||||
+12
-10
@@ -1,15 +1,17 @@
|
|||||||
/*jshint node:true*/
|
'use strict';
|
||||||
/* global require, module */
|
|
||||||
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
||||||
|
|
||||||
module.exports = function(defaults) {
|
module.exports = function(defaults) {
|
||||||
var app = new EmberApp(defaults, {
|
let app = new EmberApp(defaults, {
|
||||||
// Add options here
|
// sassOptions: {
|
||||||
sassOptions: {
|
// includePaths: [
|
||||||
includePaths: [
|
// 'node_modules/bourbon/app/assets/stylesheets'
|
||||||
'bower_components/bourbon/app/assets/stylesheets',
|
// ]
|
||||||
'bower_components/neat/app/assets/stylesheets'
|
// },
|
||||||
]
|
sourcemaps: {
|
||||||
|
enabled: true,
|
||||||
|
extensions: ['js']
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"node": true,
|
||||||
|
"browser": false
|
||||||
|
}
|
||||||
Generated
+20211
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user