Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ece573ab3c |
No files matched your search
@@ -4,6 +4,7 @@
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
|
||||
+1
-1
@@ -5,5 +5,5 @@
|
||||
|
||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
||||
*/
|
||||
"disableAnalytics": true
|
||||
"disableAnalytics": false
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# unconventional js
|
||||
/blueprints/*/files/
|
||||
/vendor/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
/release/
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
|
||||
# misc
|
||||
/coverage/
|
||||
!.*
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -1,63 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true
|
||||
}
|
||||
},
|
||||
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',
|
||||
'ember/no-jquery': 'error',
|
||||
'ember/require-computed-property-dependencies': 'warn',
|
||||
'ember/no-observers': 'warn'
|
||||
},
|
||||
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'
|
||||
},
|
||||
env: {
|
||||
browser: false,
|
||||
node: true
|
||||
},
|
||||
plugins: ['node'],
|
||||
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
|
||||
// add your custom rules and overrides for node files here
|
||||
|
||||
// this can be removed once the following is fixed
|
||||
// https://github.com/mysticatea/eslint-plugin-node/issues/77
|
||||
'node/no-unpublished-require': 'off'
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
template: |
|
||||
## Changes
|
||||
|
||||
$CHANGES
|
||||
+7
-16
@@ -1,26 +1,17 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
/dist
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
/node_modules
|
||||
/bower_components
|
||||
|
||||
# misc
|
||||
/.env*
|
||||
/.pnp*
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/
|
||||
/coverage/*
|
||||
/libpeerconnection.log
|
||||
/npm-debug.log*
|
||||
/testem.log
|
||||
/yarn-error.log
|
||||
.tm_properties
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
npm-debug.log*
|
||||
testem.log
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"predef": [
|
||||
"document",
|
||||
"window",
|
||||
"-Promise"
|
||||
],
|
||||
"browser": true,
|
||||
"boss": true,
|
||||
"curly": true,
|
||||
"debug": false,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false,
|
||||
"eqnull": true,
|
||||
"esversion": 6,
|
||||
"unused": true
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: 'octane',
|
||||
|
||||
rules: {
|
||||
'simple-unless': false,
|
||||
'no-nested-interactive': false
|
||||
},
|
||||
|
||||
ignore: [
|
||||
'kredits-web/templates/components/**',
|
||||
'app/templates/components/**'
|
||||
],
|
||||
|
||||
pending: [
|
||||
{
|
||||
"moduleId": "app/templates/dashboard",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/components/add-contribution/template",
|
||||
"only": [
|
||||
"no-action",
|
||||
"no-curly-component-invocation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/components/add-contributor/template",
|
||||
"only": [
|
||||
"no-action",
|
||||
"no-curly-component-invocation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/components/contribution-list/template",
|
||||
"only": [
|
||||
"no-action",
|
||||
"no-curly-component-invocation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/components/contributor-list/template",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/components/topbar-account-panel/template",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/templates/contributions/new",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/templates/contributions/resubmit",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/templates/contributors/edit",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/templates/contributors/new",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/templates/signup/eth-account",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/templates/signup/index",
|
||||
"only": [
|
||||
"no-action"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
+14
-16
@@ -1,27 +1,25 @@
|
||||
---
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
- "6"
|
||||
|
||||
sudo: false
|
||||
dist: xenial
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: always
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.npm
|
||||
- $HOME/.cache # includes bowers cache
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
before_install:
|
||||
- npm config set spin false
|
||||
- npm install -g bower
|
||||
- bower --version
|
||||
- npm install phantomjs-prebuilt
|
||||
- node_modules/phantomjs-prebuilt/bin/phantomjs --version
|
||||
|
||||
env:
|
||||
global:
|
||||
# See https://git.io/vdao3 for details.
|
||||
- JOBS=1
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
install:
|
||||
- npm install
|
||||
- bower install
|
||||
|
||||
script:
|
||||
- npm test
|
||||
@@ -10,24 +10,22 @@ A short introduction of this app could easily go here.
|
||||
You will need the following things properly installed on your computer.
|
||||
|
||||
* [Git](https://git-scm.com/)
|
||||
* [Node.js](https://nodejs.org/) (with npm)
|
||||
* [Node.js](https://nodejs.org/) (with NPM)
|
||||
* [Bower](https://bower.io/)
|
||||
* [Ember CLI](https://ember-cli.com/)
|
||||
* [Google Chrome](https://google.com/chrome/)
|
||||
* [PhantomJS](http://phantomjs.org/)
|
||||
|
||||
## Installation
|
||||
|
||||
* `git clone git@github.com:67P/kredits-web.git` this repository
|
||||
* `git clone <repository-url>` this repository
|
||||
* `cd kredits-web`
|
||||
* `npm install`
|
||||
* `bower install`
|
||||
|
||||
## Running / Development
|
||||
|
||||
|
||||
* `ember serve` - by default kredits-web connects to the Ethreum Kovan network
|
||||
* `ember serve`
|
||||
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
||||
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
|
||||
|
||||
See [working with locally deployed contracts](https://github.com/67P/kredits-web#working-with-locally-deployed-contracts) for details on how to develop with locally deployed contracts.
|
||||
|
||||
### Code Generators
|
||||
|
||||
@@ -38,12 +36,6 @@ Make use of the many generators for code, try `ember help generate` for more det
|
||||
* `ember test`
|
||||
* `ember test --server`
|
||||
|
||||
### Linting
|
||||
|
||||
* `npm run lint:hbs`
|
||||
* `npm run lint:js`
|
||||
* `npm run lint:js -- --fix`
|
||||
|
||||
### Building
|
||||
|
||||
* `ember build` (development)
|
||||
@@ -51,72 +43,11 @@ Make use of the many generators for code, try `ember help generate` for more det
|
||||
|
||||
### Deploying
|
||||
|
||||
_(You need collaborator permissions on the 5apps Deploy project.)_
|
||||
|
||||
`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.
|
||||
|
||||
* Make sure CORS headers are configured. See [IPFS](#ipfs) for more info.
|
||||
* `ipfs daemon`
|
||||
|
||||
#### 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 address.
|
||||
If that is the case the DAO address must be provided as `KREDITS_DAO_ADDRESS` environment variable.
|
||||
|
||||
* `npm run dao:address` in the kredits-contracts repo to find your address
|
||||
* `KREDITS_DAO_ADDRESS=<0xYOURADDRESS> npm run start:local`
|
||||
|
||||
#### 4. Metamask network
|
||||
|
||||
Switch the network in Metamask to "Custom RPC" with the RPC URL `http://localhost:7545`.
|
||||
|
||||
#### 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"]'
|
||||
ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8080
|
||||
Specify what it takes to deploy your app.
|
||||
|
||||
## Further Reading / Useful Links
|
||||
|
||||
* [ember.js](https://emberjs.com/)
|
||||
* [ember.js](http://emberjs.com/)
|
||||
* [ember-cli](https://ember-cli.com/)
|
||||
* Development Browser Extensions
|
||||
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||
|
||||
+13
-7
@@ -1,12 +1,18 @@
|
||||
import Application from '@ember/application';
|
||||
import Resolver from 'ember-resolver';
|
||||
import Ember from 'ember';
|
||||
import Resolver from './resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from './config/environment';
|
||||
|
||||
export default class App extends Application {
|
||||
modulePrefix = config.modulePrefix;
|
||||
podModulePrefix = config.podModulePrefix;
|
||||
Resolver = Resolver;
|
||||
}
|
||||
let App;
|
||||
|
||||
Ember.MODEL_FACTORY_INJECTIONS = true;
|
||||
|
||||
App = Ember.Application.extend({
|
||||
modulePrefix: config.modulePrefix,
|
||||
podModulePrefix: config.podModulePrefix,
|
||||
Resolver
|
||||
});
|
||||
|
||||
loadInitializers(App, config.modulePrefix);
|
||||
|
||||
export default App;
|
||||
@@ -1,78 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import { assign } from '@ember/polyfills';
|
||||
import moment from 'moment';
|
||||
|
||||
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', moment().startOf('hour').toDate());
|
||||
this.set('defaultAttr', {
|
||||
contributorId: null,
|
||||
kind: null,
|
||||
date: this.defaultDate,
|
||||
amount: null,
|
||||
description: null,
|
||||
url: null,
|
||||
details: null
|
||||
});
|
||||
|
||||
this.set('attributes', assign({}, this.defaultAttr, this.attributes));
|
||||
|
||||
this.reset();
|
||||
},
|
||||
|
||||
reset () {
|
||||
this.setProperties(this.attributes);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
submit (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
if (!this.isValid) {
|
||||
alert('Invalid data. Please review and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = this.getProperties(Object.keys(this.attributes));
|
||||
|
||||
let dateInput = (attributes.date instanceof Array) ?
|
||||
attributes.date[0] : attributes.date;
|
||||
|
||||
const [ date, time ] = dateInput.toISOString().split('T');
|
||||
[ attributes.date, attributes.time ] = [ date, time ];
|
||||
|
||||
this.set('inProgress', true);
|
||||
|
||||
this.save(attributes)
|
||||
.then((/*contribution*/) => {
|
||||
this.reset();
|
||||
}, err => {
|
||||
console.warn(err);
|
||||
window.alert('Something went wrong. Check the browser console for details.');
|
||||
})
|
||||
.finally(() => this.set('inProgress', false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
<form onsubmit={{action "submit"}}>
|
||||
<label>
|
||||
<p class="label">Contributor:</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut this.contributorId) value="target.value"}}>
|
||||
<option value="" selected disabled hidden></option>
|
||||
{{#each @contributors as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Kind:</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut this.kind) value="target.value"}}>
|
||||
<option value="" selected disabled hidden></option>
|
||||
<option value="bureaucracy" selected={{eq this.kind "bureaucracy"}}>Bureaucracy</option>
|
||||
<option value="community" selected={{eq this.kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq this.kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq this.kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq this.kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq this.kind "ops"}}>IT Operations</option>
|
||||
<option value="outreach" selected={{eq this.kind "outreach"}}>Outreach</option>
|
||||
<option value="qa" selected={{eq this.kind "qa"}}>Quality Assurance</option>
|
||||
<option value="special" selected={{eq this.kind "special"}}>Special</option>
|
||||
</select>
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Date:</p>
|
||||
<p>
|
||||
{{ember-flatpickr
|
||||
date=this.date
|
||||
defaultDate=this.defaultDate
|
||||
maxDate=this.defaultDate
|
||||
enableTime=true
|
||||
time_24hr=true
|
||||
onChange=(action (mut this.date))
|
||||
}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Amount:</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="500"
|
||||
value=this.amount
|
||||
class=(if this.isValidAmount "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Description:</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
value=this.description
|
||||
class=(if this.isValidDescription "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">URL (optional):</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
value=this.url
|
||||
class=(if this.isValidUrl "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
|
||||
{{#if this.details}}
|
||||
<label>
|
||||
<p class="label">Details:</p>
|
||||
<p>
|
||||
<pre>
|
||||
{{this.details}}
|
||||
</pre>
|
||||
</p>
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<p class="actions">
|
||||
{{input type="submit"
|
||||
disabled=this.inProgress
|
||||
value=(if this.inProgress "Processing" "Save")}}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -1,91 +1,33 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isPresent } from '@ember/utils';
|
||||
import { isAddress } from 'web3-utils';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Component.extend({
|
||||
export default Ember.Component.extend({
|
||||
|
||||
router: service(),
|
||||
kredits: service(),
|
||||
id: null,
|
||||
realName: null,
|
||||
address: null,
|
||||
|
||||
attributes: null,
|
||||
classId: function() {
|
||||
let value = this.get('id');
|
||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
||||
}.property('id'),
|
||||
|
||||
isValidAccount: computed('account', function() {
|
||||
return isAddress(this.account);
|
||||
}),
|
||||
classRealName: function() {
|
||||
let value = this.get('realName');
|
||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
||||
}.property('realName'),
|
||||
|
||||
isValidName: notEmpty('name'),
|
||||
isValidURL: notEmpty('url'),
|
||||
isValidGithubUID: notEmpty('github_uid'),
|
||||
isValidGithubUsername: notEmpty('github_username'),
|
||||
isValidGiteaUsername: notEmpty('gitea_username'),
|
||||
isValidWikiUsername: notEmpty('wiki_username'),
|
||||
isValidZoomDisplayName: notEmpty('zoom_display_name'),
|
||||
classAddress: function() {
|
||||
let value = this.get('address');
|
||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
||||
}.property('address'),
|
||||
|
||||
isValid: and(
|
||||
'isValidAccount',
|
||||
'isValidName',
|
||||
'isValidGithubUID'
|
||||
),
|
||||
|
||||
inProgress: false,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.setDefaultAttributes();
|
||||
this.reset();
|
||||
},
|
||||
|
||||
setDefaultAttributes () {
|
||||
if (isPresent(this.attributes)) { return; }
|
||||
|
||||
this.set('attributes', {
|
||||
account: null,
|
||||
name: null,
|
||||
kind: 'person',
|
||||
url: null,
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
gitea_username: null,
|
||||
wiki_username: null,
|
||||
zoom_display_name: null
|
||||
});
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.setProperties(this.attributes);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
submit (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
if (!this.kredits.currentUserIsCore) {
|
||||
window.alert('Only core team members can edit profiles. Please ask someone to set you up.');
|
||||
return;
|
||||
}
|
||||
if (!this.isValid) {
|
||||
window.alert('Invalid data. Please review and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = Object.keys(this.attributes);
|
||||
const contributor = this.getProperties(attributes);
|
||||
|
||||
this.set('inProgress', true);
|
||||
|
||||
this.save(contributor).then(() => {
|
||||
this.reset();
|
||||
}).catch(err => {
|
||||
console.warn(err);
|
||||
window.alert('Something went wrong. Please check the browser console.');
|
||||
}).finally(() => {
|
||||
this.set('inProgress', false);
|
||||
this.router.transitionTo('dashboard');
|
||||
});
|
||||
save() {
|
||||
console.log('id', this.get('id'));
|
||||
console.log('realName', this.get('realName'));
|
||||
console.log('address', this.get('address'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,56 +1,26 @@
|
||||
<form onsubmit={{action "submit"}}>
|
||||
<form {{action "save" on="submit"}}>
|
||||
<p>
|
||||
<label for="c-account">Ethereum account</label>
|
||||
<Input @type="text"
|
||||
@value={{this.account}}
|
||||
name="account" id="c-account"
|
||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||
class={{if this.isValidAccount "valid" ""}} />
|
||||
{{input name="id"
|
||||
type="text"
|
||||
placeholder="GitHub UID (123)"
|
||||
value=id
|
||||
class=classId}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-kind">Kind</label>
|
||||
<select required onchange={{action (mut this.kind) value="target.value"}} id="c-kind">
|
||||
<option value="person" selected={{eq this.kind "person"}}>Person</option>
|
||||
<option value="organization" selected={{eq this.kind "organization"}}>Organization</option>
|
||||
</select>
|
||||
{{input name="realname"
|
||||
type="text"
|
||||
placeholder="Carl Sagan"
|
||||
value=realName
|
||||
class=classRealName}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-name">Name</label>
|
||||
{{input name="name" type="text" value=this.name placeholder="Zero Cool"
|
||||
class=(if this.isValidName "valid" "") id="c-name"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-url">URL</label>
|
||||
{{input name="url" type="text" value=this.url placeholder="http://zerocool.bit"
|
||||
class=(if this.isValidURL "valid" "") id="c-url"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-github-uid">GitHub UID</label>
|
||||
{{input name="github_uid" type="text" value=this.github_uid placeholder="2342"
|
||||
class=(if this.isValidGithubUID "valid" "") id="c-github-uid"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-github-username">GitHub username</label>
|
||||
{{input name="github_username" type="text" value=this.github_username placeholder="zerocool"
|
||||
class=(if this.isValidGithubUsername "valid" "") id="c-github-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-gitea-username">Gitea username</label>
|
||||
{{input name="gitea_username" type="text" value=this.gitea_username placeholder="zerocool"
|
||||
class=(if this.isValidGiteaUsername "valid" "") id="c-gitea-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-wiki-username">Wiki username</label>
|
||||
{{input name="wiki_username" type="text" value=this.wiki_username placeholder="ZeroCool"
|
||||
class=(if this.isValidWikiUsername "valid" "") id="c-wiki-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-zoom-display-name">Zoom display name</label>
|
||||
{{input name="zoom_display_name" type="text" value=this.zoom_display_name placeholder="Zero Cool"
|
||||
class=(if this.isValidZoomDisplayName "valid" "") id="c-zoom-display-name"}}
|
||||
{{input name="address"
|
||||
type="text"
|
||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||
value=address
|
||||
class=classAddress}}
|
||||
</p>
|
||||
<p class="actions">
|
||||
{{input type="submit" disabled=this.inProgress
|
||||
value=(if this.inProgress "Processing" "Save")}}
|
||||
<input type="submit" value="Save">
|
||||
</p>
|
||||
</form>
|
||||
@@ -1,64 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
let categoryColors = {
|
||||
community: "#fb6868",
|
||||
design: "#fbe468",
|
||||
dev: "#e068fb",
|
||||
docs: "#97fb68",
|
||||
ops: "#8f68fb",
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
contributions: null,
|
||||
chartOptions: Object.freeze({
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}),
|
||||
|
||||
chartData: computed('contributions', function() {
|
||||
let kredits = this.contributions
|
||||
.filterBy('vetoed', false)
|
||||
.map(c => {
|
||||
return { kind: c.kind, amount: c.amount }
|
||||
})
|
||||
.reduce(function (kinds, c) {
|
||||
if (c.kind in kinds) {
|
||||
kinds[c.kind] = kinds[c.kind] + c.amount
|
||||
} else {
|
||||
kinds[c.kind] = c.amount;
|
||||
}
|
||||
return kinds;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
datasets: [{
|
||||
data: [
|
||||
kredits['community'],
|
||||
kredits['design'],
|
||||
kredits['dev'],
|
||||
kredits['ops'],
|
||||
kredits['docs'],
|
||||
],
|
||||
borderColor: [
|
||||
categoryColors.community,
|
||||
categoryColors.design,
|
||||
categoryColors.dev,
|
||||
categoryColors.ops,
|
||||
categoryColors.docs,
|
||||
],
|
||||
borderWidth: 1
|
||||
}],
|
||||
labels: [
|
||||
'Community',
|
||||
'Design',
|
||||
'Development',
|
||||
'Operations & Infrastructure',
|
||||
'Documentation'
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="chart">
|
||||
<EmberChart @type="doughnut"
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@width="200" @height="200" />
|
||||
</div>
|
||||
@@ -1,70 +0,0 @@
|
||||
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({
|
||||
|
||||
router: service(),
|
||||
|
||||
tagName: 'div',
|
||||
classNames: ['contributions'],
|
||||
|
||||
selectedContribution: null,
|
||||
|
||||
showQuickFilter: false,
|
||||
hideSmallContributions: false,
|
||||
contributorId: null,
|
||||
contributionKind: null,
|
||||
|
||||
kredits: service(),
|
||||
|
||||
contributorsSorting: Object.freeze(['name:asc']),
|
||||
contributors: sort('kredits.contributors', 'contributorsSorting'),
|
||||
|
||||
contributorsActive: computed('contributors.[]', 'contributions', function() {
|
||||
const activeIds = new Set(this.contributions.mapBy('contributorId')
|
||||
.map(id => id.toString()));
|
||||
|
||||
return this.contributors.filter(c => activeIds.has(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.');
|
||||
}
|
||||
},
|
||||
|
||||
openContributionDetails(contribution) {
|
||||
this.router.transitionTo('dashboard.contributions.show', contribution);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
{{#if @showQuickFilter}}
|
||||
<div class="quick-filter">
|
||||
<p>
|
||||
<label class="filter-contributor">
|
||||
Contributor:
|
||||
<select onchange={{action (mut this.contributorId) value="target.value"}}>
|
||||
<option value="" selected>all</option>
|
||||
{{#each this.contributorsActive as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="filter-contribution-kind">
|
||||
Kind:
|
||||
<select onchange={{action (mut this.contributionKind) value="target.value"}}>
|
||||
<option value="" selected>all</option>
|
||||
{{#each this.contributionKinds as |kind|}}
|
||||
<option value={{kind}} selected={{eq this.contributionKind kind}}>{{capitalize-string kind}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="filter-contribution-size">
|
||||
<Input @type="checkbox" @checked={{this.hideSmallContributions}} />
|
||||
Hide small contributions
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<ul class="contribution-list {{if @loading 'loading'}}">
|
||||
{{#each this.contributionsFiltered as |contribution|}}
|
||||
<li role="button" data-contribution-id={{contribution.id}}
|
||||
{{action "openContributionDetails" contribution}}
|
||||
class="{{contribution-status contribution}}{{if (eq contribution.id @selectedContributionId) " selected"}}">
|
||||
<p class="meta">
|
||||
<span class="recipient"><UserAvatar @contributor={{contribution.contributor}} /></span>
|
||||
<span class="category {{contribution.kind}}">({{contribution.kind}})</span>
|
||||
<span class="title">{{contribution.description}}</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">
|
||||
{{input type="button" class="button small danger" value="veto"
|
||||
click=(action "veto" contribution.id)
|
||||
disabled=contribution.hasPendingChanges}}
|
||||
</p>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
@@ -1,18 +1,8 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
export default Ember.Component.extend({
|
||||
|
||||
router: service(),
|
||||
tagName: 'table',
|
||||
classNames: 'contributor-list'
|
||||
|
||||
selectedContributorId: null,
|
||||
|
||||
actions: {
|
||||
|
||||
openContributorDetails(contributor) {
|
||||
this.router.transitionTo('dashboard.contributors.show', contributor);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@@ -1,25 +1,14 @@
|
||||
<table class="contributor-list {{if @loading 'loading'}}">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each @contributorList as |c|}}
|
||||
<tr role="button"
|
||||
onclick={{action "openContributorDetails" c.contributor}}
|
||||
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id @selectedContributorId) "selected"}}">
|
||||
<td class="person">
|
||||
<UserAvatar @contributor={{c.contributor}} /> {{c.contributor.name}}
|
||||
</td>
|
||||
<td class="kredits">
|
||||
<span class="amount">
|
||||
{{#if @showUnconfirmedKredits}}
|
||||
{{c.amountTotal}}
|
||||
{{else}}
|
||||
{{c.amountConfirmed}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="symbol">₭S</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<tbody>
|
||||
{{#each contributors as |contributor|}}
|
||||
<tr>
|
||||
<td class="person">
|
||||
<img class="avatar" src={{contributor.avatarURL}}>
|
||||
{{contributor.github_username}}
|
||||
</td>
|
||||
<td class="kredits">
|
||||
<span class="amount">{{contributor.kredits}}</span>
|
||||
<span class="symbol">₭S</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
@@ -1,15 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
account: null,
|
||||
|
||||
iconComponentName: computed('account.site', function() {
|
||||
if (this.account.site.match(/github|gitea|wiki|zoom/)) {
|
||||
return 'icon-account-' + this.account.site.replace(/\./g, '-');
|
||||
} else {
|
||||
return 'icon-web-globe';
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
<a href={{this.account.url}} target="_blank" rel="noopener noreferrer"
|
||||
title="{{this.account.username}} on {{this.account.site}}">
|
||||
<span class="site">{{this.account.site}}</span>
|
||||
{{component this.iconComponentName}}
|
||||
</a>
|
||||
@@ -1,6 +1,6 @@
|
||||
import Component from '@ember/component';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Component.extend({
|
||||
export default Ember.Component.extend({
|
||||
|
||||
classNames: ['loading-spinner']
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" x="0px" y="0px" viewBox="0 0 132 100"
|
||||
enable-background="new 0 0 100 100" xml:space="preserve">
|
||||
<path id="path-comet"
|
||||
<path id="path-comet"
|
||||
d="M79.062,24.173L79.062,24.173l-0.021-0.011c-0.068-0.03-0.137-0.062-0.204-0.093L42.392,7.337
|
||||
c0,0,3.065,14.104,4.458,18.019l0,0c0,0-41.754-12.481-46.85-13.67c1.67,6.52,30.607,62.492,30.607,62.492
|
||||
c5.848,11.873,19.394,18.485,33.522,18.485c19.811,0,35.87-16.059,35.87-35.869C100,42.313,91.418,29.837,79.062,24.173z">
|
||||
@@ -10,5 +10,5 @@
|
||||
</svg>
|
||||
</p>
|
||||
<p>
|
||||
Loading data...
|
||||
Loading data from Ethereum...
|
||||
</p>
|
||||
@@ -0,0 +1,16 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
tagName: 'ul',
|
||||
classNames: ['proposal-list'],
|
||||
|
||||
actions: {
|
||||
|
||||
confirm(proposalId) {
|
||||
this.sendAction('confirmAction', proposalId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{{#each proposals as |proposal|}}
|
||||
<li>
|
||||
<span class="id">#{{proposal.id}}</span>
|
||||
Issue{{#if proposal.executed}}d{{/if}}
|
||||
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
|
||||
to <span class="recipient" title="{{proposal.recipientAddress}}">{{proposal.recipientName}}</span>
|
||||
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
|
||||
|
||||
{{#unless proposal.executed}}<button {{action "confirm" proposal.id}}>+1</button>{{/unless}}
|
||||
</li>
|
||||
{{/each}}
|
||||
@@ -1,46 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
import { isPresent } from '@ember/utils';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
tagName: '',
|
||||
|
||||
kredits: service(),
|
||||
router: service(),
|
||||
|
||||
setupInProgress: false,
|
||||
|
||||
userHasEthereumWallet: computed(function() {
|
||||
return isPresent(window.ethereum);
|
||||
}),
|
||||
|
||||
showConnectButton: computed('userHasEthereumWallet',
|
||||
'kredits.hasAccounts', function() {
|
||||
return this.userHasEthereumWallet &&
|
||||
!this.kredits.hasAccounts;
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
||||
signup() {
|
||||
this.router.transitionTo('signup');
|
||||
},
|
||||
|
||||
async connectAccount() {
|
||||
try {
|
||||
await window.ethereum.enable();
|
||||
this.set('setupInProgress', true);
|
||||
await this.kredits.setup();
|
||||
this.set('setupInProgress', false);
|
||||
this.router.transitionTo('dashboard');
|
||||
} catch (error) {
|
||||
this.set('setupInProgress', false);
|
||||
console.log('Opening Ethereum wallet failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
<section id="user-account">
|
||||
{{#if this.setupInProgress}}
|
||||
Connecting account...
|
||||
{{else}}
|
||||
{{#if (and this.kredits.hasAccounts this.kredits.currentUser)}}
|
||||
{{this.kredits.currentUser.name}}
|
||||
{{#if this.kredits.currentUserIsCore}}
|
||||
<span class="core-flag">(core)</span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="anonymous">Anonymous</span>
|
||||
<button onclick={{action "signup"}} class="small" type="button">Sign up</button>
|
||||
{{#if this.showConnectButton}}
|
||||
<button onclick={{action "connectAccount"}} class="small green" type="button">Connect account</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</section>
|
||||
@@ -1,33 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
const SIZES = {
|
||||
'small': '128', // pixels
|
||||
'medium': '256',
|
||||
'large': '512'
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
contributor: null,
|
||||
tagName: 'img',
|
||||
classNames: ['avatar'],
|
||||
classNameBindings: ['size'],
|
||||
attributeBindings: ['src', 'title'],
|
||||
size: 'small',
|
||||
|
||||
src: alias('avatarURL'),
|
||||
title: alias('contributor.name'),
|
||||
|
||||
avatarURL: computed('contributor.github_uid', 'size', function() {
|
||||
const github_uid = this.contributor.github_uid;
|
||||
|
||||
if (github_uid) {
|
||||
return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=${SIZES[this.size]}`;
|
||||
} else {
|
||||
// TODO use custom avatar
|
||||
return '';
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
Whitespace-only changes.
@@ -1,6 +1,4 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Controller.extend({
|
||||
kredits: service(),
|
||||
export default Ember.Controller.extend({
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { alias, filterBy, sort } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
queryParams: ['contributorId', 'kind', 'amount'],
|
||||
|
||||
contributors: alias('kredits.contributors'),
|
||||
minedContributors: filterBy('contributors', 'id'),
|
||||
|
||||
contributorsSorting: Object.freeze(['name:asc']),
|
||||
sortedContributors: sort('minedContributors', 'contributorsSorting'),
|
||||
|
||||
actions: {
|
||||
|
||||
save (contribution) {
|
||||
const contributor = this.contributors.findBy('id', contribution.contributorId);
|
||||
contribution.contributorIpfsHash = contributor.ipfsHash;
|
||||
|
||||
return this.kredits.addContribution(contribution)
|
||||
.then(contribution => {
|
||||
this.transitionToRoute('index');
|
||||
return contribution;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import ContributionsNewController from 'kredits-web/controllers/contributions/new';
|
||||
|
||||
export default ContributionsNewController.extend({
|
||||
|
||||
attributes: null,
|
||||
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
actions: {
|
||||
|
||||
save (attributes) {
|
||||
return this.kredits.updateContributor(this.model.id, attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
actions: {
|
||||
|
||||
save (contributor) {
|
||||
return this.kredits.addContributor(contributor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, not, sort } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
kredits: service(),
|
||||
|
||||
showDetailsPane: false,
|
||||
selectedContributorId: null,
|
||||
selectedContributionId: null,
|
||||
|
||||
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,
|
||||
|
||||
showIntroText: computed('kredits.{hasAccounts,currentUser}', function(){
|
||||
return (!this.kredits.hasAccounts || !this.kredits.currentUser);
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
||||
vetoContribution (contributionId) {
|
||||
this.kredits.veto(contributionId).then(transaction => {
|
||||
console.debug('[controllers:index] Veto submitted to Ethereum blockhain: '+transaction.hash);
|
||||
});
|
||||
},
|
||||
|
||||
toggleQuickFilterUnconfirmed () {
|
||||
this.toggleProperty('showQuickFilterUnconfirmed');
|
||||
},
|
||||
|
||||
toggleQuickFilterConfirmed () {
|
||||
this.toggleProperty('showQuickFilterConfirmed');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
ipfsGatewayUrl: computed(function() {
|
||||
return config.ipfs.gatewayUrl;
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
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';
|
||||
}),
|
||||
|
||||
ipfsGatewayUrl: computed(function() {
|
||||
return config.ipfs.gatewayUrl;
|
||||
})
|
||||
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
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.recipientName = this.findContributorByAddress(p.recipientAddress).github_username;
|
||||
return p;
|
||||
});
|
||||
return proposals;
|
||||
}.property('model.proposals.[]', 'model.contributors.[]'),
|
||||
|
||||
proposalsClosed: function() {
|
||||
let proposals = this.get('model.proposals')
|
||||
.filterBy('executed', true)
|
||||
.map(p => {
|
||||
p.recipientName = this.findContributorByAddress(p.recipientAddress).github_username;
|
||||
return p;
|
||||
});
|
||||
return proposals;
|
||||
}.property('model.proposals.[]', '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'),
|
||||
|
||||
actions: {
|
||||
|
||||
confirmProposal(proposalId) {
|
||||
this.get('kredits').vote(proposalId).then(transactionId => {
|
||||
window.confirm('Vote submitted to Ethereum blockhain: '+transactionId);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, not } from '@ember/object/computed';
|
||||
import { isAddress } from 'web3-utils';
|
||||
import { inject as service } from '@ember/service';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
ethAddress: null,
|
||||
githubAccessToken: alias('kredits.githubAccessToken'),
|
||||
|
||||
isValidEthAccount: computed('ethAddress', function() {
|
||||
return isAddress(this.ethAddress);
|
||||
}),
|
||||
|
||||
signupButtonDisabled: not('isValidEthAccount'),
|
||||
|
||||
actions: {
|
||||
|
||||
completeSignup () {
|
||||
const payload = {
|
||||
accessToken: this.githubAccessToken,
|
||||
account: this.ethAddress
|
||||
}
|
||||
|
||||
fetch(config.githubSignupUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
console.log('Created contributor:', data);
|
||||
|
||||
this.setProperties({
|
||||
githubAccessToken: null,
|
||||
ethAddress: null
|
||||
});
|
||||
|
||||
this.transitionToRoute('signup.complete');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
actions: {
|
||||
|
||||
connectGithub () {
|
||||
window.location = config.githubConnectUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
|
||||
export default Helper.extend({
|
||||
compute([string]) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { once } from '@ember/runloop';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
kredits: service(),
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
compute([contribution]) {
|
||||
this.setupRecompute(contribution);
|
||||
|
||||
let status = [];
|
||||
|
||||
if (contribution.vetoed) {
|
||||
status.push('vetoed');
|
||||
} else if (contribution.confirmedAt > this.currentBlock) {
|
||||
status.push('unconfirmed');
|
||||
} else {
|
||||
status.push('confirmed');
|
||||
}
|
||||
|
||||
if (contribution.hasPendingChanges) {
|
||||
status.push('pending');
|
||||
}
|
||||
|
||||
return status.join(' ');
|
||||
},
|
||||
|
||||
destroy () {
|
||||
if (this.teardown) this.teardown();
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
setupRecompute (contribution) {
|
||||
if (this.teardown) this.teardown();
|
||||
|
||||
contribution.addObserver('vetoed' , this, this.triggerRecompute);
|
||||
contribution.addObserver('confirmedAt' , this, this.triggerRecompute);
|
||||
contribution.addObserver('currentBlock' , this, this.triggerRecompute);
|
||||
contribution.addObserver('hasPendingChanges' , this, this.triggerRecompute);
|
||||
|
||||
this.teardown = () => {
|
||||
contribution.removeObserver('vetoed', this, this.triggerRecompute);
|
||||
contribution.removeObserver('confirmedAt', this, this.triggerRecompute);
|
||||
contribution.removeObserver('currentBlock', this, this.triggerRecompute);
|
||||
contribution.removeObserver('hasPendingChanges', this, this.triggerRecompute);
|
||||
};
|
||||
},
|
||||
|
||||
triggerRecompute () {
|
||||
once(this, this.recompute);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
kredits: service(),
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
compute([contribution]) {
|
||||
return !contribution.vetoed &&
|
||||
(contribution.confirmedAt <= this.currentBlock);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { isPresent } from '@ember/utils';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
kredits: service(),
|
||||
currentUser: alias('kredits.currentUser'),
|
||||
|
||||
compute([contributor]) {
|
||||
return isPresent(contributor) && isPresent(this.currentUser) &&
|
||||
contributor.account === this.currentUser.account;
|
||||
}
|
||||
|
||||
});
|
||||
+3
-9
@@ -3,22 +3,16 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Kosmos Kredits</title>
|
||||
<title>KreditsWeb</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
|
||||
{{content-for "head"}}
|
||||
|
||||
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
|
||||
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
||||
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
|
||||
<link rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
||||
|
||||
{{content-for "head-footer"}}
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
window.windowLoadComplete = true;
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
{{content-for "body"}}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import EmberObject, { computed } from '@ember/object';
|
||||
import { isEmpty, isPresent } from '@ember/utils';
|
||||
import moment from 'moment';
|
||||
|
||||
export default EmberObject.extend({
|
||||
|
||||
// Contract
|
||||
id: null,
|
||||
contributorId: null,
|
||||
amount: null,
|
||||
confirmedAt: null,
|
||||
vetoed: null,
|
||||
ipfsHash: null,
|
||||
|
||||
// contributor model instance
|
||||
contributor: null,
|
||||
|
||||
// TODO contributor who submitted the contribution
|
||||
// submittedBy: null,
|
||||
|
||||
// IPFS
|
||||
kind: null,
|
||||
description: null,
|
||||
details: null,
|
||||
url: null,
|
||||
date: null,
|
||||
time: null,
|
||||
|
||||
pendingTx: null,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
if (isEmpty(this.details)) this.set('details', {});
|
||||
},
|
||||
|
||||
iso8601Date: computed('date', 'time', function() {
|
||||
return this.time ? `${this.date}T${this.time}` : this.date;
|
||||
}),
|
||||
|
||||
jsDate: computed('iso8601Date', function() {
|
||||
return moment(this.iso8601Date).toDate();
|
||||
}),
|
||||
|
||||
hasPendingChanges: computed('pendingTx', function() {
|
||||
return isPresent(this.pendingTx);
|
||||
}),
|
||||
|
||||
serialize () {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,26 +1,14 @@
|
||||
import EmberObject from '@ember/object';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default EmberObject.extend({
|
||||
// Contract
|
||||
id: null,
|
||||
account: null,
|
||||
balance: 0,
|
||||
totalKreditsEarned: 0,
|
||||
contributionsCount: 0,
|
||||
isCore: false,
|
||||
ipfsHash: null,
|
||||
export default Ember.Object.extend({
|
||||
|
||||
// IPFS
|
||||
kind: null,
|
||||
name: null,
|
||||
url: null,
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
wiki_username: null,
|
||||
zoom_display_name: null,
|
||||
kredits: null,
|
||||
address: null,
|
||||
|
||||
serialize () {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
avatarURL: function() {
|
||||
return `https\:\/\/avatars2.githubusercontent.com/u/${this.get('github_uid')}?v=3&s=128`;
|
||||
}.property('github_uid')
|
||||
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Object.extend({
|
||||
|
||||
id: null,
|
||||
creator: null,
|
||||
recipient: null,
|
||||
votesCount: null,
|
||||
votesNeeded: null,
|
||||
amount: null,
|
||||
executed: null,
|
||||
url: null,
|
||||
ipfsHash: null
|
||||
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import Resolver from 'ember-resolver';
|
||||
|
||||
export default Resolver;
|
||||
+8
-27
@@ -1,32 +1,13 @@
|
||||
import EmberRouter from '@ember/routing/router';
|
||||
import Ember from 'ember';
|
||||
import config from './config/environment';
|
||||
|
||||
export default class Router extends EmberRouter {
|
||||
location = config.locationType;
|
||||
rootURL = config.rootURL;
|
||||
}
|
||||
const Router = Ember.Router.extend({
|
||||
location: config.locationType,
|
||||
rootURL: config.rootURL
|
||||
});
|
||||
|
||||
Router.map(function() {
|
||||
this.route('dashboard', function() {
|
||||
this.route('contributors', function() {
|
||||
this.route('show', { path: ':id' });
|
||||
});
|
||||
|
||||
this.route('contributions', function() {
|
||||
this.route('show', { path: ':id' });
|
||||
});
|
||||
});
|
||||
this.route('contributions', function() {
|
||||
this.route('new', { queryParams: ['contributorId', 'kind', 'amount'] });
|
||||
this.route('resubmit', { path: ':id/resubmit' });
|
||||
});
|
||||
this.route('contributors', function() {
|
||||
this.route('new');
|
||||
this.route('edit', { path: ':id/edit' });
|
||||
});
|
||||
this.route('signup', function() {
|
||||
this.route('github');
|
||||
this.route('eth-account');
|
||||
this.route('complete');
|
||||
});
|
||||
this.route('spinner');
|
||||
});
|
||||
|
||||
export default Router;
|
||||
@@ -1,39 +1,4 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Route.extend({
|
||||
kredits: service(),
|
||||
|
||||
beforeModel(/* transition */) {
|
||||
const kredits = this.kredits;
|
||||
|
||||
return kredits.setup().then(() => {
|
||||
kredits.get('kredits').preflightChecks().catch((error) => {
|
||||
console.error('Kredits preflight check failed!');
|
||||
console.error(error);
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.log('Error initializing Kredits', error);
|
||||
});
|
||||
},
|
||||
|
||||
afterModel() {
|
||||
return this.kredits.loadInitialData()
|
||||
.then(() => {
|
||||
this.kredits.addContractEventHandlers();
|
||||
})
|
||||
.then(() => {
|
||||
if (this.kredits.contributorsNeedSync) {
|
||||
schedule('afterRender', this.kredits.syncContributors,
|
||||
this.kredits.syncContributors.perform);
|
||||
}
|
||||
if (this.kredits.contributionsNeedSync) {
|
||||
schedule('afterRender', this.kredits.syncContributions,
|
||||
this.kredits.syncContributions.perform);
|
||||
}
|
||||
schedule('afterRender', this.kredits.fetchMissingContributions,
|
||||
this.kredits.fetchMissingContributions.perform);
|
||||
});
|
||||
}
|
||||
export default Ember.Route.extend({
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
model (params) {
|
||||
return { params };
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
model(params) {
|
||||
const contribution = this.kredits.contributions.findBy('id', parseInt(params.id));
|
||||
contribution.contributorId = contribution.contributorId.toString();
|
||||
|
||||
return contribution;
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
controller.set('attributes', model.getProperties([
|
||||
'kind', 'amount', 'description', 'url', 'details'
|
||||
]));
|
||||
controller.set('attributes.contributorId', model.contributorId.toString());
|
||||
controller.set('attributes.date', model.jsDate);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
contributors: alias('kredits.contributors'),
|
||||
|
||||
model(params) {
|
||||
return this.kredits.contributors.findBy('id', params.id);
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
controller.set('attributes', {
|
||||
account: model.account,
|
||||
name: model.name,
|
||||
kind: model.kind,
|
||||
url: model.url,
|
||||
github_username: model.github_username,
|
||||
github_uid: model.github_uid,
|
||||
gitea_username: model.gitea_username,
|
||||
wiki_username: model.wiki_username,
|
||||
zoom_display_name: model.zoom_display_name
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
contributions: alias('kredits.contributions'),
|
||||
|
||||
model (params) {
|
||||
return this.contributions.findBy('id', parseInt(params.id));
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
this.controllerFor('dashboard')
|
||||
.setProperties({
|
||||
showDetailsPane: true,
|
||||
selectedContributionId: model.id
|
||||
});
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
this.controllerFor('dashboard')
|
||||
.setProperties({
|
||||
showDetailsPane: false,
|
||||
selectedContributionId: null
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
contributors: alias('kredits.contributors'),
|
||||
|
||||
model (params) {
|
||||
return this.contributors.findBy('id', params.id);
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
this.controllerFor('dashboard')
|
||||
.setProperties({
|
||||
showDetailsPane: true,
|
||||
selectedContributorId: model.id
|
||||
});
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
this.controllerFor('dashboard')
|
||||
.setProperties({
|
||||
showDetailsPane: false,
|
||||
selectedContributorId: null
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
+13
-4
@@ -1,9 +1,18 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Route.extend({
|
||||
export default Ember.Route.extend({
|
||||
|
||||
redirect () {
|
||||
this.transitionTo('dashboard');
|
||||
kredits: Ember.inject.service(),
|
||||
|
||||
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 Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
redirect () {
|
||||
this._super(...arguments);
|
||||
|
||||
if (isEmpty(this.kredits.githubAccessToken)) {
|
||||
this.transitionTo('signup.index');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
redirect () {
|
||||
this._super(...arguments);
|
||||
|
||||
let accessToken;
|
||||
try {
|
||||
accessToken = window.location.hash.match(/access_token=(.+)/)[1];
|
||||
} catch (error) { /* ignore */ }
|
||||
|
||||
if (isEmpty(accessToken) || accessToken === 'undefined') {
|
||||
console.error('No GitHub access token found.');
|
||||
this.transitionTo('signup');
|
||||
return;
|
||||
}
|
||||
|
||||
this.kredits.set('githubAccessToken', accessToken);
|
||||
|
||||
this.transitionTo('signup.eth-account');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import Service from '@ember/service';
|
||||
import * as localforage from 'localforage';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
function createStore(name) {
|
||||
const networkName = config.web3RequiredNetwork || 'custom';
|
||||
return localforage.createInstance({ name: `kredits:${networkName}:${name}` });
|
||||
}
|
||||
|
||||
export default class BrowserCacheService extends Service {
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.stores = {
|
||||
contributors: createStore('contributors'),
|
||||
contributions: createStore('contributions')
|
||||
}
|
||||
}
|
||||
|
||||
get contributors() {
|
||||
return this.stores.contributors;
|
||||
}
|
||||
|
||||
get contributions() {
|
||||
return this.stores.contributions;
|
||||
}
|
||||
}
|
||||
+107
-465
@@ -1,496 +1,138 @@
|
||||
import ethers from 'ethers';
|
||||
import Kredits from 'kredits-contracts';
|
||||
|
||||
import Service from '@ember/service';
|
||||
import EmberObject from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, notEmpty } from '@ember/object/computed';
|
||||
import { isEmpty, isPresent } from '@ember/utils';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import { task, taskGroup } from 'ember-concurrency';
|
||||
|
||||
import groupBy from 'kredits-web/utils/group-by';
|
||||
import processContributorData from 'kredits-web/utils/process-contributor-data';
|
||||
import processContributionData from 'kredits-web/utils/process-contribution-data';
|
||||
import formatKredits from 'kredits-web/utils/format-kredits';
|
||||
|
||||
import Ember from 'ember';
|
||||
import Web3 from 'npm:web3';
|
||||
import config from 'kredits-web/config/environment';
|
||||
import Contributor from 'kredits-web/models/contributor'
|
||||
import Contribution from 'kredits-web/models/contribution'
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
import Proposal from 'kredits-web/models/proposal';
|
||||
import kreditsContracts from 'npm:kredits-contracts';
|
||||
|
||||
export default Service.extend({
|
||||
export default Ember.Service.extend({
|
||||
|
||||
browserCache: service(),
|
||||
web3Instance: null,
|
||||
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
|
||||
|
||||
currentBlock: null,
|
||||
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
|
||||
currentUser: null,
|
||||
contributors: null,
|
||||
contributions: null,
|
||||
githubAccessToken: null,
|
||||
web3: function() {
|
||||
if (this.get('web3Instance')) {
|
||||
return this.get('web3Instance');
|
||||
}
|
||||
|
||||
currentUserIsContributor: notEmpty('currentUser'),
|
||||
currentUserIsCore: alias('currentUser.isCore'),
|
||||
hasAccounts: notEmpty('currentUserAccounts'),
|
||||
let web3Instance;
|
||||
|
||||
// When data was loaded from cache, we need to fetch updates from the network
|
||||
contributorsNeedSync: false,
|
||||
contributionsNeedSync: false,
|
||||
if (typeof window.web3 !== 'undefined') {
|
||||
Ember.Logger.debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
|
||||
web3Instance = window.web3;
|
||||
this.set('web3Provided', true);
|
||||
} else {
|
||||
Ember.Logger.debug('[kredits] Creating new instance from npm module class');
|
||||
let provider = new Web3.providers.HttpProvider(config.web3ProviderUrl);
|
||||
web3Instance = new Web3(provider);
|
||||
}
|
||||
|
||||
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||
return this.contributions.filter(contribution => {
|
||||
return contribution.confirmedAt > this.currentBlock;
|
||||
this.set('web3Instance', web3Instance);
|
||||
window.web3 = web3Instance;
|
||||
|
||||
return web3Instance;
|
||||
}.property('web3Instance'),
|
||||
|
||||
kreditsContract: function() {
|
||||
if (this.get('kreditsContractInstance')) {
|
||||
return this.get('kreditsContractInstance');
|
||||
}
|
||||
|
||||
let contract = kreditsContracts(this.get('web3'))['Kredits'];
|
||||
|
||||
window.Kredits = contract;
|
||||
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('contributions', []);
|
||||
},
|
||||
|
||||
// This is called in the application route's beforeModel(). So it is
|
||||
// initialized before everything else, and we can rely on the ethProvider and
|
||||
// the potential currentUserAccounts to be available
|
||||
getEthProvider () {
|
||||
let ethProvider;
|
||||
|
||||
return new Promise(resolve => {
|
||||
function instantiateWithoutAccount () {
|
||||
console.debug('[kredits] Creating new instance from npm module class');
|
||||
console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`);
|
||||
ethProvider = new ethers.providers.JsonRpcProvider(config.web3ProviderUrl);
|
||||
resolve({
|
||||
ethProvider: ethProvider,
|
||||
ethSigner: null
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
if (isPresent(config.web3RequiredNetwork) &&
|
||||
network.name !== config.web3RequiredNetwork) {
|
||||
window.alert(`Please switch your Ethereum wallet to the "${config.web3RequiredNetwork}" network before connecting your account.`);
|
||||
return instantiateWithoutAccount();
|
||||
}
|
||||
|
||||
ethProvider.listAccounts().then(accounts => {
|
||||
context.set('currentUserAccounts', accounts);
|
||||
const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner();
|
||||
resolve({
|
||||
ethProvider,
|
||||
ethSigner
|
||||
getContributorData(i) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.getValueFromContract('contributorAddresses', i).then(address => {
|
||||
this.getValueFromContract('contributors', address).then(person => {
|
||||
this.getValueFromContract('balanceOf', address).then(balance => {
|
||||
let contributor = Contributor.create({
|
||||
address: address,
|
||||
github_username: person[1],
|
||||
github_uid: person[0],
|
||||
ipfsHash: person[3],
|
||||
kredits: balance.toNumber()
|
||||
});
|
||||
Ember.Logger.debug('[kredits] contributor', contributor);
|
||||
resolve(contributor);
|
||||
});
|
||||
});
|
||||
}).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));
|
||||
}
|
||||
|
||||
if (window.ethereum) {
|
||||
if (window.ethereum.isConnected()) {
|
||||
instantiateWithAccount(window.ethereum, this);
|
||||
} else {
|
||||
instantiateWithoutAccount();
|
||||
}
|
||||
}
|
||||
// Legacy dapp browsers...
|
||||
else if (window.web3) {
|
||||
instantiateWithAccount(window.web3.currentProvider, this);
|
||||
}
|
||||
// Non-dapp browsers...
|
||||
else {
|
||||
instantiateWithoutAccount();
|
||||
}
|
||||
return Ember.RSVP.all(contributors);
|
||||
});
|
||||
},
|
||||
|
||||
setup () {
|
||||
return this.getEthProvider().then((providerAndSigner) => {
|
||||
let kredits = new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
|
||||
addresses: { Kernel: config.kreditsKernelAddress },
|
||||
apm: config.kreditsApmDomain,
|
||||
ipfsConfig: config.ipfs
|
||||
});
|
||||
return kredits
|
||||
.init()
|
||||
.then(async (kredits) => {
|
||||
this.set('kredits', kredits);
|
||||
this.set('currentBlock', await kredits.provider.getBlockNumber());
|
||||
|
||||
if (this.currentUserAccounts && this.currentUserAccounts.length > 0) {
|
||||
this.getCurrentUser.then((contributorData) => {
|
||||
this.set('currentUser', contributorData);
|
||||
});
|
||||
}
|
||||
|
||||
return kredits;
|
||||
getProposalData(i) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.getValueFromContract('proposals', i).then(p => {
|
||||
let proposal = Proposal.create({
|
||||
id : i,
|
||||
creatorAddress : p[0],
|
||||
recipientAddress : p[1],
|
||||
votesCount : p[2].toNumber(),
|
||||
votesNeeded : p[3].toNumber(),
|
||||
amount : p[4].toNumber(),
|
||||
executed : p[5],
|
||||
url : p[6],
|
||||
ipfsHash : p[7]
|
||||
});
|
||||
Ember.Logger.debug('[kredits] proposal', proposal);
|
||||
resolve(proposal);
|
||||
}).catch(err => reject(err));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
totalSupply: computed(function() {
|
||||
return this.kredits.Token.functions.totalSupply().then(total => {
|
||||
return formatKredits(total);
|
||||
})
|
||||
}),
|
||||
getProposals() {
|
||||
return this.getValueFromContract('proposalsCount').then(proposalsCount => {
|
||||
let proposals = [];
|
||||
|
||||
totalKreditsEarned: computed(function() {
|
||||
return this.kredits.Contribution.functions.totalKreditsEarned(true)
|
||||
.then(total => total.toNumber());
|
||||
}),
|
||||
|
||||
async loadInitialData () {
|
||||
const numCachedContributors = await this.browserCache.contributors.length();
|
||||
if (numCachedContributors > 0) {
|
||||
await this.loadContributorsFromCache();
|
||||
this.set('contributorsNeedSync', true);
|
||||
} else {
|
||||
await this.fetchContributors();
|
||||
}
|
||||
|
||||
const numCachedContributions = await this.browserCache.contributions.length();
|
||||
if (numCachedContributions > 0) {
|
||||
await this.loadContributionsFromCache();
|
||||
this.set('contributionsNeedSync', true);
|
||||
} else {
|
||||
await this.fetchContributions({ page: { size: 30 } });
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
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);
|
||||
});
|
||||
},
|
||||
|
||||
fetchContributors () {
|
||||
console.debug(`[kredits] Fetching all contributors from the network`);
|
||||
return this.kredits.Contributor.all()
|
||||
.then(contributors => {
|
||||
return contributors.forEach(data => {
|
||||
this.loadContributorFromData(data);
|
||||
return;
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return this.cacheLoadedContributors();
|
||||
});
|
||||
},
|
||||
|
||||
loadContributorFromData(data) {
|
||||
const contributor = Contributor.create(processContributorData(data));
|
||||
const loadedContributor = this.contributors.findBy('id', contributor.id);
|
||||
if (loadedContributor) { this.contributors.removeObject(loadedContributor); }
|
||||
this.contributors.pushObject(contributor);
|
||||
return contributor;
|
||||
},
|
||||
|
||||
async cacheLoadedContributors () {
|
||||
for (const c of this.contributors) {
|
||||
await this.browserCache.contributors.setItem(c.id, c.serialize());
|
||||
}
|
||||
console.debug(`[kredits] Cached ${this.contributors.length} contributors in browser storage`);
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
async loadContributorsFromCache () {
|
||||
return this.browserCache.contributors.iterate((value/*, key , iterationNumber */) => {
|
||||
this.contributors.pushObject(Contributor.create(JSON.parse(value)));
|
||||
}).then((/* result */) => {
|
||||
console.debug(`[kredits] Loaded ${this.contributors.length} contributors from cache`);
|
||||
});
|
||||
},
|
||||
|
||||
syncContributors: task(function * () {
|
||||
yield this.fetchContributors();
|
||||
}),
|
||||
|
||||
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);
|
||||
contribution.set('pendingTx', data);
|
||||
contribution.set('confirmedAtBlock', data.blockNumber + 40320);
|
||||
this.contributions.pushObject(contribution);
|
||||
return contribution;
|
||||
});
|
||||
},
|
||||
|
||||
fetchContributions (options = { page: { size: 200 } }) {
|
||||
console.debug(`[kredits] Fetching contributions from the network`);
|
||||
return this.kredits.Contribution.all(options)
|
||||
.then(contributions => {
|
||||
return contributions.map(data => {
|
||||
const contribution = this.loadContributionFromData(data);
|
||||
return contribution;
|
||||
});
|
||||
})
|
||||
.then(contributions => {
|
||||
const cacheWrites = contributions.map(c => {
|
||||
return this.browserCache.contributions.setItem(c.id.toString(), c.serialize());
|
||||
});
|
||||
return Promise.all(cacheWrites).then(() => {
|
||||
console.debug(`[kredits] Cached ${contributions.length} contributions in browser storage`);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
loadContributionFromData(data) {
|
||||
const contribution = Contribution.create(processContributionData(data));
|
||||
contribution.set('contributor', this.contributors.findBy('id', data.contributorId.toString()));
|
||||
const loadedContribution = this.contributions.findBy('id', contribution.id);
|
||||
if (loadedContribution) { this.contributions.removeObject(loadedContribution); }
|
||||
this.contributions.pushObject(contribution);
|
||||
return contribution;
|
||||
},
|
||||
|
||||
async cacheLoadedContributions () {
|
||||
for (const c of this.contributions) {
|
||||
await this.browserCache.contributions.setItem(c.id, c.serialize());
|
||||
}
|
||||
console.debug(`[kredits] Cached ${this.contributions.length} contributions in browser storage`);
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
async loadContributionsFromCache () {
|
||||
return this.browserCache.contributions.iterate((value/*, key , iterationNumber */) => {
|
||||
this.contributions.pushObject(Contribution.create(JSON.parse(value)));
|
||||
}).then((/* result */) => {
|
||||
console.debug(`[kredits] Loaded ${this.contributions.length} contributions from cache`);
|
||||
});
|
||||
},
|
||||
|
||||
contributionTasks: taskGroup().enqueue(),
|
||||
|
||||
syncContributions: task(function * () {
|
||||
yield this.fetchNewContributions.perform();
|
||||
yield this.syncUnconfirmedContributions.perform();
|
||||
}).group('contributionTasks'),
|
||||
|
||||
fetchNewContributions: task(function * () {
|
||||
const count = yield this.kredits.Contribution.functions.contributionsCount();
|
||||
const lastKnownContributionId = Math.max.apply(null, this.contributions.mapBy('id'));
|
||||
const toFetch = count - lastKnownContributionId;
|
||||
|
||||
if (toFetch > 0) {
|
||||
console.debug(`[kredits] Fetching ${toFetch} new contributions`);
|
||||
for (let id = lastKnownContributionId; id <= count; id++) {
|
||||
const data = yield this.kredits.Contribution.getById(id);
|
||||
const c = this.loadContributionFromData(data);
|
||||
yield this.browserCache.contributions.setItem(c.id.toString(), c.serialize());
|
||||
for(var i = 0; i < proposalsCount.toNumber(); i++) {
|
||||
proposals.push(this.getProposalData(i));
|
||||
}
|
||||
} else {
|
||||
console.debug(`[kredits] No new contributions to fetch`);
|
||||
}
|
||||
}),
|
||||
|
||||
fetchMissingContributions: task(function * () {
|
||||
const count = yield this.kredits.Contribution.functions.contributionsCount();
|
||||
const allIds = [...Array(count+1).keys()];
|
||||
allIds.shift(); // remove first item, which is 0
|
||||
const loadedContributions = new Set(this.contributions.mapBy('id'));
|
||||
const toFetch = allIds.filter(id => !loadedContributions.has(id));
|
||||
if (toFetch.length === 0) {
|
||||
console.debug(`[kredits] No contributions left to fetch`);
|
||||
return;
|
||||
}
|
||||
console.debug(`[kredits] Fetching ${toFetch.length} past contributions`);
|
||||
let countFetched = 0;
|
||||
|
||||
for (let id = count; id > 0; id--) {
|
||||
if (loadedContributions.has(id)) {
|
||||
continue;
|
||||
} else {
|
||||
const data = yield this.kredits.Contribution.getById(id);
|
||||
const c = this.loadContributionFromData(data);
|
||||
yield this.browserCache.contributions.setItem(c.id.toString(), c.serialize());
|
||||
countFetched++;
|
||||
if (countFetched % 20 === 0) {
|
||||
console.debug(`[kredits] Fetched ${countFetched} more contributions`);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.debug(`[kredits] Cached ${countFetched} past contributions`);
|
||||
}).group('contributionTasks'),
|
||||
|
||||
syncUnconfirmedContributions: task(function * () {
|
||||
if (this.contributionsUnconfirmed.length > 0) {
|
||||
console.debug(`[kredits] Syncing unconfirmed contributions`);
|
||||
for (const c of this.contributionsUnconfirmed) {
|
||||
const data = yield this.kredits.Contribution.getById(c.id);
|
||||
const contribution = this.loadContributionFromData(data);
|
||||
yield this.browserCache.contributions.setItem(c.id.toString(), contribution.serialize());
|
||||
}
|
||||
} else {
|
||||
console.debug(`[kredits] No unconfirmed contributions to sync`);
|
||||
}
|
||||
}),
|
||||
|
||||
veto (contributionId) {
|
||||
console.debug('[kredits] veto against', contributionId);
|
||||
const contribution = this.contributions.findBy('id', contributionId);
|
||||
|
||||
return this.kredits.Contribution.functions.veto(contributionId, { gasLimit: 300000 })
|
||||
.then(data => {
|
||||
console.debug('[kredits] veto response', data);
|
||||
contribution.set('pendingTx', data);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
getCurrentUser: computed('kredits.provider', 'currentUserAccounts.[]', function() {
|
||||
if (isEmpty(this.currentUserAccounts)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.kredits.Contributor
|
||||
.functions.getContributorIdByAddress(this.currentUserAccounts.firstObject)
|
||||
.then((id) => {
|
||||
// check if the user is a contributor or not
|
||||
if (id === 0) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.kredits.Contributor.getById(id);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
// 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('ContributionAdded', this.handleContributionAdded.bind(this))
|
||||
.on('ContributionVetoed', this.handleContributionVetoed.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);
|
||||
},
|
||||
|
||||
async handleContributionAdded (id, contributorId, amount) {
|
||||
console.debug('[kredits] ContributionAdded event received', { id, contributorId, amount });
|
||||
|
||||
const pendingContribution = this.contributions.find(c => {
|
||||
return (c.id === null) &&
|
||||
(c.contributorId.toString() === contributorId.toString()) &&
|
||||
(c.amount.toString() === amount.toString());
|
||||
return Ember.RSVP.all(proposals);
|
||||
});
|
||||
|
||||
if (pendingContribution) {
|
||||
const attributes = await this.kredits.Contribution.getById(id);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId.toString());
|
||||
const newContribution = Contribution.create(attributes);
|
||||
this.contributions.addObject(newContribution);
|
||||
this.contributions.removeObject(pendingContribution);
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
contribution.set('pendingTx', null);
|
||||
}
|
||||
vote(proposalId) {
|
||||
Ember.Logger.debug('[kredits] vote for', proposalId);
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.get('kreditsContract').vote(proposalId, (err, data) => {
|
||||
if (err) { reject(err); }
|
||||
Ember.Logger.debug('[kredits] vote response', data);
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleTransfer (from, to, value) {
|
||||
value = value.toNumber();
|
||||
logKreditsContract: function() {
|
||||
Ember.Logger.debug('[kredits] kreditsContract', this.get('kreditsContract'));
|
||||
}.on('init')
|
||||
|
||||
this.contributors
|
||||
.findBy('address', from)
|
||||
.decrementProperty('balance', value);
|
||||
|
||||
this.contributors
|
||||
.findBy('address', to)
|
||||
.incrementProperty('balance', value);
|
||||
},
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
button, input[type=submit], .button {
|
||||
display: inline-block;
|
||||
padding: 0.6rem 2rem;
|
||||
background-color: rgba(22, 21, 40, 0.6);
|
||||
border: 1px solid rgba(22, 21, 40, 1);
|
||||
border-radius: 3px;
|
||||
color: $primary-color;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.1em;
|
||||
-webkit-appearance: none;
|
||||
|
||||
&+button, &+input[type=submit], &+.button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(22, 21, 40, 0.8);
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: 0.86rem;
|
||||
padding: 0.2rem 0.8rem;
|
||||
}
|
||||
|
||||
&.danger:not(:disabled) {
|
||||
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:not(:disabled) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
&.icon {
|
||||
svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 1rem;
|
||||
|
||||
.fg {
|
||||
fill: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
$blue: #68d7fb;
|
||||
$purple: #8f68fb;
|
||||
$pink: #e068fb;
|
||||
$green: #97fb68;
|
||||
$yellow: #fbe468;
|
||||
$red: #fb6868;
|
||||
|
||||
$primary-color: $blue;
|
||||
|
||||
$body-text-color: #fff;
|
||||
@@ -1,115 +0,0 @@
|
||||
section#add-contributor,
|
||||
section#add-contribution,
|
||||
section#signup {
|
||||
|
||||
form {
|
||||
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.label {
|
||||
margin-bottom: .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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+18
-141
@@ -1,154 +1,31 @@
|
||||
main {
|
||||
padding: 1rem 2rem;
|
||||
|
||||
@include media-max(small) {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
&#dashboard {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-row-gap: 2rem;
|
||||
grid-template-areas:
|
||||
"stats"
|
||||
"contributions";
|
||||
}
|
||||
|
||||
&.center-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
section {
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
.content {
|
||||
a {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
&.text-lg {
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#intro {
|
||||
padding: 2rem;
|
||||
background-color: rgba(0,0,0,.2);
|
||||
font-size: 1.6rem;
|
||||
|
||||
@include media-max(small) {
|
||||
padding: 2rem 1rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: inherit;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.5em;
|
||||
font-size: 1.2rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover, &:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
main {
|
||||
&#dashboard {
|
||||
grid-column-gap: 4rem;
|
||||
grid-row-gap: 2rem;
|
||||
grid-template-columns: 2fr 4fr;
|
||||
grid-template-areas:
|
||||
"stats contributions";
|
||||
|
||||
&.with-details {
|
||||
grid-column-gap: 3rem;
|
||||
grid-template-columns: 2fr 4fr 2fr;
|
||||
grid-template-areas:
|
||||
"stats contributions details";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main section {
|
||||
margin-bottom: 5rem;
|
||||
|
||||
section {
|
||||
@include outer-container;
|
||||
margin-bottom: 8rem;
|
||||
&:first-of-type {
|
||||
margin-top: 2rem;
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
@include media($mobile) {
|
||||
margin-bottom: 5rem;
|
||||
&:first-of-type {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 3rem;
|
||||
@include span-columns(12);
|
||||
padding-bottom: 3rem;
|
||||
text-align: center;
|
||||
// background-color: purple;
|
||||
|
||||
&.with-nav {
|
||||
display: grid;
|
||||
grid-template-columns: auto 5rem;
|
||||
grid-template-areas:
|
||||
"title" "actions";
|
||||
@include media($mobile) {
|
||||
@include span-columns(10);
|
||||
@include shift(1);
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
display: inline-block;
|
||||
// padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
margin-bottom: 2rem;
|
||||
div.content {
|
||||
@include span-columns(12);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
$breakpoints-max: (
|
||||
small: 600px,
|
||||
medium: 960px,
|
||||
large: 1280px
|
||||
);
|
||||
|
||||
$breakpoints-min: (
|
||||
small: 601px,
|
||||
medium: 961px,
|
||||
large: 1281px
|
||||
);
|
||||
|
||||
@mixin media-max($screen-size) {
|
||||
@if map-has-key($breakpoints-max, $screen-size) {
|
||||
@media (max-width: map-get($breakpoints-max, $screen-size)) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
// Debugging
|
||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
||||
}
|
||||
}
|
||||
|
||||
@mixin media-min($screen-size) {
|
||||
@if map-has-key($breakpoints-min, $screen-size) {
|
||||
@media (min-width: map-get($breakpoints-min, $screen-size)) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
// Debugging
|
||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
@mixin loading-border-top {
|
||||
&::before {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.2) 40%, #68d7fb 60%, rgba(255, 255, 255, 0.2));
|
||||
background-size: 200% 200%;
|
||||
animation: kitt 2.5s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kitt {
|
||||
0%{ background-position: 0% 0%}
|
||||
50%{ background-position: 100% 0%}
|
||||
100%{ background-position: 0% 0%}
|
||||
}
|
||||
+36
-48
@@ -10,23 +10,22 @@
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@import "mediaqueries";
|
||||
@import "bourbon";
|
||||
@import "settings/neat";
|
||||
@import "neat";
|
||||
@import "settings/breakpoints";
|
||||
@import "layout";
|
||||
@import "colors";
|
||||
|
||||
$font-family-sans: 'Open Sans', sans-serif;
|
||||
|
||||
html, body {
|
||||
font-family: $font-family-sans;
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
color: $body-text-color;
|
||||
}
|
||||
|
||||
body {
|
||||
background-image: linear-gradient(to bottom, rgba(22, 21, 40, .4), rgba(0, 0, 0, .75)), url('/img/bg.jpg');
|
||||
background-image: url('/img/bg.jpg');
|
||||
background-repeat: none;
|
||||
background-attachment: fixed;
|
||||
font-family: $font-family-sans;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
@@ -41,61 +40,50 @@ h1, h2, h3, h4, h5, input, button {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
section {
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
color: $primary-color;
|
||||
font-size: 2.8rem;
|
||||
color: lightblue;
|
||||
@include media($mobile) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&#people {
|
||||
&#contributors {
|
||||
.content {
|
||||
p.stats {
|
||||
margin-bottom: 1rem;
|
||||
padding-top: 3rem;
|
||||
font-size: 1rem;
|
||||
color: white;
|
||||
color: lightblue;
|
||||
text-align: center;
|
||||
span.number {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&#contributions-by-type {
|
||||
.chart {
|
||||
width: 50%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@include media-max(small) {
|
||||
width: 75%;
|
||||
@include media($mobile) {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
section {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
button, input[type=submit] {
|
||||
display: inline-block;
|
||||
border: 1px solid rgba(22, 21, 40, 1);
|
||||
background-color: rgba(22, 21, 40, 0.6);
|
||||
color: 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 "buttons";
|
||||
@import "forms";
|
||||
@import "sugar";
|
||||
|
||||
@import "components/contribution-list";
|
||||
@import "components/contribution-details";
|
||||
@import "components/contributor-list";
|
||||
@import "components/contributor-profile";
|
||||
@import "components/external-account-link";
|
||||
@import "components/loading-spinner";
|
||||
@import "components/topbar";
|
||||
@import "components/topbar-account-panel";
|
||||
@import "components/user-avatar";
|
||||
@import "components/contributor-list";
|
||||
@import "components/add-contributor";
|
||||
@import "components/proposal-list";
|
||||
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
section#contribution-details {
|
||||
header {
|
||||
nav {
|
||||
.amount {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
margin: 0 0 1.5rem;
|
||||
padding: 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);
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
span {
|
||||
}
|
||||
|
||||
&.who-what-when {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a:not(.button) {
|
||||
color: $primary-color;
|
||||
text-decoration: none;
|
||||
|
||||
&:not(.button):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.vetoed {
|
||||
.content {
|
||||
h3 {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hint.vetoed {
|
||||
overflow: auto;
|
||||
margin-top: 2rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
font-size: 1.2rem;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On small screens, hide intro text, contributor and contributions list when
|
||||
// showing details
|
||||
@include media-max(small) {
|
||||
#dashboard.with-details {
|
||||
#contributions, #stats {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
#intro.with-details {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
main section {
|
||||
@include media-max(small) {
|
||||
&#contributions-unconfirmed, &#contributions-confirmed {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quick-filter {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
p {
|
||||
padding: 0.2rem 0 0;
|
||||
}
|
||||
|
||||
label {
|
||||
&+ 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);
|
||||
cursor: pointer;
|
||||
|
||||
&: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;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
p {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
line-height: 2rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
&.title {
|
||||
a {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
@include loading-border-top;
|
||||
|
||||
li {
|
||||
&:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,39 @@
|
||||
table.contributor-list {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
tr {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
cursor: pointer;
|
||||
|
||||
&:first-of-type {
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
&.current-user {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0 1.2rem;
|
||||
line-height: 3.6rem;
|
||||
|
||||
&.person {
|
||||
text-align: left;
|
||||
font-size: 1.2rem;
|
||||
|
||||
img.avatar {
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.kredits {
|
||||
text-align: right;
|
||||
|
||||
.amount {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
font-size: 0.8rem;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
@include loading-border-top;
|
||||
&::before {
|
||||
position: absolute;
|
||||
td {
|
||||
padding: 0 1.2rem;
|
||||
line-height: 4.2rem;
|
||||
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
&.person {
|
||||
text-align: left;
|
||||
font-size: 1.4rem;
|
||||
img.avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
&.kredits {
|
||||
text-align: right;
|
||||
.amount {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.symbol {
|
||||
font-size: 1rem;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
section#contributor-profile {
|
||||
text-align: center;
|
||||
|
||||
header {
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
margin: 0 auto;
|
||||
border: 3px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
margin: -7.2rem 0 1.5rem;
|
||||
padding: 6rem 1.2rem 2rem;
|
||||
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
p.role {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
ul.external-accounts {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
|
||||
&+li {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
.button {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
.external-accounts a {
|
||||
display: inline-block;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
padding: 0.8rem;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: $body-text-color;
|
||||
border-radius: 2rem;
|
||||
|
||||
span.site {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fg {
|
||||
fill: $body-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
svg {
|
||||
.fg {
|
||||
fill: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@
|
||||
margin-top: 12rem;
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
color: $primary-color;
|
||||
color: lightblue;
|
||||
|
||||
@include media-max(small) {
|
||||
@include media($mobile) {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
margin-bottom: 2rem;
|
||||
|
||||
#path-comet {
|
||||
fill: $primary-color;
|
||||
fill: lightblue;
|
||||
opacity: 0.1;
|
||||
|
||||
animation-name: pulse;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
ul.proposal-list {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
// display: block;
|
||||
padding: 0 1.2rem;
|
||||
line-height: 4.2rem;
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
&:first-of-type {
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.id {
|
||||
color: lightblue;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.amount {
|
||||
font-weight: 500;
|
||||
}
|
||||
.symbol {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
.recipient {
|
||||
font-weight: 500;
|
||||
}
|
||||
.votes {
|
||||
font-size: 1rem;
|
||||
color: lightblue;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
margin-top: 1.1rem;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
padding: 0 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
header#topbar section#user-account {
|
||||
|
||||
button {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
button + button {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
.anonymous {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#topbar {
|
||||
padding: 0 1rem;
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
background-color: rgba(0,0,0,.3);
|
||||
|
||||
@include media-min(medium) {
|
||||
padding: 0 2rem;
|
||||
height: 4rem;
|
||||
line-height: 4rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
section#user-account {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
img.avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
&.medium {
|
||||
margin: 0;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
border-radius: 5rem;
|
||||
}
|
||||
|
||||
&.large {
|
||||
margin: 0;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
border-radius: 128px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
$mobile: new-breakpoint(max-width 600px);
|
||||
@@ -0,0 +1,5 @@
|
||||
// $visual-grid: true;
|
||||
// $visual-grid-color: red;
|
||||
// $visual-grid-index: front;
|
||||
// $visual-grid-opacity: 0.5;
|
||||
$max-width: em(500, 12);
|
||||
@@ -1 +0,0 @@
|
||||
<LoadingSpinner />
|
||||
@@ -1,6 +1,3 @@
|
||||
<header id="topbar">
|
||||
<h1><LinkTo @route="dashboard">Kosmos Kredits</LinkTo></h1>
|
||||
<TopbarAccountPanel />
|
||||
</header>
|
||||
|
||||
{{outlet}}
|
||||
<main>
|
||||
{{outlet}}
|
||||
</main>
|
||||
File renamed without changes.
@@ -1,6 +0,0 @@
|
||||
<svg class="icon-gitea" width="400" height="400" version="1.1" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g fill-rule="evenodd">
|
||||
<path d="m235.61 136.7 7e-3 32.7 4.493 2.189c2.471 1.204 4.628 2.25 4.793 2.324s0.3-15.626 0.3-34.889v-35.024h-9.6l7e-3 32.7m-22.336 30.528c-2.787 0.548-5.894 2.175-8.11 4.244-2.296 2.145-1.694 0.967-24.197 47.328l-11.164 23v4.6c0 4.305 0.076 4.75 1.185 6.942 2.815 5.56 1.055 4.606 69.415 37.629l4.2 2.029h4.221c10.104 0 11.144-1.319 26.676-33.8 0.999-2.09 4.15-8.57 7.001-14.4s6.916-14.2 9.033-18.6l3.849-8 0.01-4.219c0.02-8.49-2.644-11.721-14.326-17.371-4.215-2.038-11.264-5.461-15.664-7.606-15.241-7.429-22.802-11.07-23.125-11.137-0.418-0.086-1.177 1.299-5.112 9.333l-3.234 6.6 1.19 2.2c0.654 1.21 1.308 3.038 1.454 4.063l0.265 1.862 5.747 2.8c22.047 10.742 22.975 15.314 9.007 44.377l-3.317 6.902 1.162 2.098c3.865 6.974-0.923 15.098-8.898 15.098-12.557 0-13.717-18.335-1.278-20.2l1.842-0.276 3.101-6.462c12.037-25.079 11.953-25.952-3.232-33.53l-7.333-3.659-2.319 1.032c-1.276 0.567-3.151 1.153-4.167 1.301l-1.848 0.269-6.785 13.855-6.786 13.854 1.109 1.747c6.109 9.633-5.532 20.771-14.552 13.924-6.729-5.108-4.39-16.742 3.633-18.073 0.812-0.134 1.71-0.318 1.996-0.408 0.429-0.136 6.592-12.372 13.118-26.044l1.37-2.87-0.782-1.33c-3.586-6.099 0.331-14.253 7.353-15.306l1.683-0.252 3.969-8.045c2.183-4.425 4.019-8.168 4.08-8.319s-2.979-1.758-6.755-3.571c-8.092-3.885-10.4-4.452-14.685-3.609" />
|
||||
<path class="fg" d="m73.4 97.876c-2.903 0.376-8.068 1.377-8.8 1.705-0.44 0.197-2.915 1.007-5.5 1.8s-4.7 1.617-4.7 1.831c0 0.213-0.225 0.388-0.5 0.388-0.63 1e-3 -5.615 2.344-5.893 2.771-0.114 0.175-0.837 0.638-1.607 1.029-5.482 2.788-14.835 11.523-18.63 17.4-0.639 0.99-1.274 1.89-1.409 2-0.659 0.534-4.752 8.933-5.555 11.4-0.502 1.54-1.048 2.98-1.213 3.2-0.587 0.782-2.031 7.531-2.489 11.633-0.256 2.292-0.61 4.167-0.785 4.167-0.443 0-0.403 17.45 0.042 18.305 0.199 0.382 0.566 2.748 0.816 5.258 0.493 4.939 1.847 12.516 2.428 13.578 0.198 0.363 0.83 2.369 1.405 4.459 1.927 7.009 10.26 22.927 12.44 23.764 0.303 0.116 0.55 0.481 0.55 0.812 0 0.813 10.667 11.552 12.3 12.383 0.715 0.364 1.3 0.821 1.3 1.017 0 0.195 0.945 0.934 2.1 1.642s2.19 1.41 2.3 1.561 1.28 0.875 2.6 1.609c1.32 0.735 2.67 1.552 3 1.816s1.95 1.086 3.6 1.827c1.65 0.74 3.101 1.486 3.224 1.658 0.123 0.171 1.741 0.865 3.596 1.542s3.557 1.384 3.783 1.572c0.226 0.187 2.388 0.907 4.804 1.599s4.753 1.423 5.193 1.624c1.299 0.593 7.258 1.856 12.2 2.586 2.53 0.374 4.96 0.83 5.4 1.013 1.151 0.48 8.262 1.165 15.067 1.45l5.866 0.247 0.877 2.594c0.903 2.673 4.652 10.529 5.195 10.884 0.168 0.11 0.585 0.83 0.928 1.6 0.685 1.538 1.927 3.686 2.313 4 0.135 0.11 0.826 1.19 1.536 2.4 1.544 2.634 4.561 7.175 5.293 7.968 0.289 0.313 0.975 1.236 1.525 2.051 1.729 2.563 7.039 9.405 8.557 11.025 0.244 0.26 1.523 1.692 2.843 3.183 3.476 3.925 7.354 7.774 10.261 10.183 1.397 1.157 2.629 2.223 2.739 2.368s1.096 0.884 2.191 1.643c1.094 0.758 2.33 1.681 2.745 2.051s1.229 0.911 1.809 1.203 1.884 1.026 2.897 1.632c1.952 1.167 7.201 3.093 8.428 3.093 0.402 0 0.73 0.18 0.73 0.4 0 0.511 115.46 0.557 115.77 0.046 0.121-0.194 1.074-0.482 2.119-0.639 2.437-0.365 7.765-2.307 9.27-3.378 0.64-0.456 1.334-0.829 1.542-0.829 0.451 0 5.639-3.687 8.597-6.111 2.854-2.337 5.633-5.107 8.718-8.689 1.42-1.65 2.681-3.09 2.8-3.2 0.581-0.536 5.161-6.76 5.812-7.9 0.409-0.715 0.869-1.3 1.023-1.3 0.153 0 0.938-1.125 1.742-2.5 2.068-3.533 4.108-6.822 4.561-7.352 0.213-0.248 1.043-1.755 1.845-3.348s1.62-3.1 1.818-3.348c0.687-0.864 5.069-9.661 5.555-11.152 0.269-0.825 0.642-1.5 0.829-1.5s0.878-1.396 1.536-3.101c0.658-1.706 1.671-4.047 2.251-5.203 0.581-1.156 1.405-3.116 1.832-4.356 0.426-1.239 0.934-2.453 1.129-2.696 0.194-0.244 0.946-2.154 1.671-4.244s1.446-3.89 1.602-4c0.155-0.11 0.964-2.36 1.797-5 0.832-2.64 1.627-4.89 1.766-5s0.861-2.27 1.604-4.8c0.742-2.53 1.484-4.782 1.649-5.003 0.164-0.222 0.872-2.562 1.573-5.2 0.701-2.639 1.412-4.977 1.58-5.197s0.965-3.19 1.77-6.6 1.623-6.488 1.817-6.84c0.195-0.353 0.919-3.503 1.611-7 0.692-3.498 1.431-6.678 1.642-7.068 0.211-0.389 0.938-4.169 1.616-8.4 0.677-4.23 1.388-8.052 1.579-8.492 0.192-0.44 0.708-3.77 1.147-7.4 4.258-35.198 1.497-55.451-8.208-60.199l-2.219-1.086-7.473 0.266c-4.111 0.146-7.834 0.417-8.274 0.602-0.801 0.336-13.842 0.941-52 2.412-11.22 0.433-22.2 0.881-24.4 0.997-2.2 0.115-11.334 0.415-20.298 0.665l-16.298 0.455-0.204 69.922-9.392-4.634-8e-3 -65.272-19.3-0.277c-10.615-0.153-19.84-0.441-20.5-0.642-0.66-0.2-6.69-0.551-13.4-0.779-21.82-0.743-58.18-2.495-59-2.843-0.44-0.187-4.04-0.453-8-0.591s-11.34-0.508-16.4-0.822c-8.365-0.519-22.619-0.685-25.6-0.298m13.657 31.23c0.269 0.173 0.809 3.742 1.313 8.694 0.471 4.62 1.021 8.76 1.221 9.2 0.201 0.44 0.561 2.42 0.801 4.4 0.709 5.856 2.382 15.937 2.787 16.8 0.207 0.44 0.943 3.68 1.636 7.2s1.4 6.58 1.571 6.8 0.803 2.56 1.405 5.2c0.601 2.64 1.333 5.34 1.627 6 0.293 0.66 1.193 3.63 2 6.6 0.808 2.97 1.597 5.49 1.754 5.6s0.884 2.27 1.614 4.8c0.731 2.53 1.455 4.69 1.608 4.8 0.154 0.11 0.868 2.09 1.587 4.4 0.72 2.31 1.494 4.429 1.721 4.71 0.226 0.28 0.341 0.567 0.255 0.637-0.263 0.215-9.713-1.339-10.37-1.704-0.337-0.188-1.237-0.468-2-0.623-4-0.814-10.947-2.703-11.712-3.185-0.481-0.304-1.325-0.675-1.875-0.825-1.377-0.377-5.355-1.954-5.6-2.22-0.11-0.12-1.447-0.841-2.971-1.604-1.524-0.762-3.216-1.791-3.759-2.286s-1.16-0.9-1.372-0.9c-1.422 0-11.329-9.68-12.558-12.27-0.347-0.731-0.775-1.33-0.951-1.33-0.593 0-4.638-8.175-5.591-11.3-0.519-1.705-1.071-3.1-1.226-3.1s-0.372-0.495-0.482-1.1c-0.111-0.605-0.599-3.08-1.084-5.5-1.119-5.58-1.528-15.681-0.814-20.111 0.516-3.208 1.895-8.141 2.372-8.489 0.15-0.11 0.752-1.19 1.338-2.4 2.053-4.245 6.611-8.836 11.098-11.177 1.43-0.746 2.69-1.485 2.8-1.643 0.28-0.402 6.231-2.23 8.4-2.58 0.99-0.16 2.003-0.454 2.251-0.654 0.248-0.199 2.678-0.513 5.4-0.697s5.039-0.357 5.149-0.385c0.11-0.029 0.406 0.08 0.657 0.242m131.79 38.066c2.262 0.371 16.092 6.678 15.866 7.236-0.061 0.151-1.897 3.894-4.08 8.319l-3.969 8.045-1.683 0.252c-7.022 1.053-10.939 9.207-7.353 15.306l0.782 1.33-1.37 2.87c-6.526 13.672-12.689 25.908-13.118 26.044-0.286 0.09-1.184 0.274-1.996 0.408-8.023 1.331-10.362 12.965-3.633 18.073 9.02 6.847 20.661-4.291 14.552-13.924l-1.109-1.747 6.786-13.854 6.785-13.855 1.848-0.269c1.016-0.148 2.891-0.734 4.167-1.301l2.319-1.032 7.333 3.659c15.185 7.578 15.269 8.451 3.232 33.53l-3.101 6.462-1.842 0.276c-12.439 1.865-11.279 20.2 1.278 20.2 7.975 0 12.763-8.124 8.898-15.098l-1.162-2.098Line truncated
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,8 +0,0 @@
|
||||
<svg class="icon-github" width="260" height="260" enable-background="new 0 0 212.4575 186.23" version="1.1" viewBox="0 0 260 260" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(23.766 36.885)">
|
||||
<path d="m151.79 86.234c-7.078 0.1229-14.154 0.5605-21.232 0.7069-12.318 0.248-24.64 0.5664-36.961 0.512-11.176-0.0472-22.358-0.9728-33.518-0.7052-7.3164 0.1776-14.373 2.2948-20.131 7.584-9.9472 9.1368-12.522 21-11.314 33.807 1.92 20.357 9.2716 31.449 29.115 37.064 15.793 4.4704 31.963 5.2088 48.199 4.9744 5.254 0 10.51 0.1367 15.758-0.0292 12.723-0.4064 25.264-2.2072 37.358-6.3596 10.846-3.7225 18.799-10.691 22.572-22.166 2.6736-8.1288 3.7148-16.388 2.5544-24.853-1.7772-12.959-13.275-30.86-32.4-30.535zm-81.576 59.996c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20zm76 0c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20z" fill="none"/>
|
||||
<path class="fg" d="m200.25 59.188c-2.5076-3.4493-4.3632-6.3888-3.8748-11.44 0.6796-7.0664-0.3536-14.396-1.3204-21.518-1.2188-8.9843-3.5236-17.74-7.1776-26.23-9.0332 1.9356-17.981 3.49-25.963 8.0976-8.4648 4.8908-16.695 10.215-25.08 15.25-0.9532 0.5704-2.3124 0.8848-3.3848 0.7032-17.967-3.1368-35.92-3.0196-53.898-0.086-1.3632 0.2244-3.0724-0.2345-4.2948-0.9608-4.4436-2.6604-8.6564-5.7344-13.123-8.3516-11.656-6.844-23.66-12.822-37.494-14.59-0.3928 0.8597-0.7188 1.4785-0.9612 2.1253-4.164 11.22-6.6424 22.844-7.4276 34.82-0.2872 4.3516-0.6092 8.8048 0 13.072 0.3456 2.4296-4e-3 3.8788-1.3924 5.6132-4.8516 6.0488-8.6368 12.717-11.102 20.219-4.6972 14.295-4.4004 28.861-2.2756 43.475 1.7268 11.974 4.7344 23.537 11.324 33.873 13.186 20.682 32.892 29.774 55.572 31.705 19.308 1.6464 38.8 1.1992 58.213 1.2364 13.855 0.0251 27.57-1.4924 40.715-6.6389 20.375-7.9727 33.859-22.604 40.275-44.166 3.7344-12.545 5.2324-25.396 4.8144-38.498-0.4412-13.81-4.0504-26.566-12.144-37.711zm-18.609 82.435c-3.7736 11.475-11.727 18.444-22.572 22.166-12.094 4.1524-24.635 5.9532-37.358 6.3596-5.248 0.1659-10.504 0.0292-15.758 0.0292-16.236 0.2344-32.406-0.504-48.199-4.9744-19.844-5.6152-27.195-16.707-29.115-37.064-1.2072-12.807 1.3672-24.67 11.314-33.807 5.7576-5.2892 12.814-7.4064 20.131-7.584 11.16-0.2676 22.342 0.658 33.518 0.7052 12.32 0.0544 24.642-0.264 36.961-0.512 7.0784-0.1464 14.154-0.584 21.232-0.7069 19.125-0.3243 30.623 17.576 32.4 30.535 1.1604 8.4647 0.1192 16.724-2.5544 24.853z"/>
|
||||
<ellipse class="fg" cx="70.214" cy="126.23" rx="16" ry="20"/>
|
||||
<ellipse class="fg" cx="146.21" cy="126.23" rx="16" ry="20"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,8 +0,0 @@
|
||||
<svg class="icon-wiki" width="120" height="120" version="1.1" viewBox="0 0 120 120" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(18.1,3)">
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="6.2 5 45.7 20.5 45.7 91 6.2 75.5"/>
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="90 5 50.5 20.5 50.5 91 90 75.5"/>
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="87 88 87 81.8 60.4 91"/>
|
||||
<polygon class="fg" transform="translate(-6.2,9)" points="9.2 88 9.2 81.8 34.9 91"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 703 B |
@@ -1,8 +0,0 @@
|
||||
<svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" 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(0 -229.27)">
|
||||
<g transform="matrix(.90278 0 0 .90278 6.5684 141.05)" fill-rule="evenodd">
|
||||
<path class="fg" d="m32.67 117.1c2.2056 0.1405 3.0528 0.27041 4.1992 0.64479 3.9359 1.2854 6.7418 4.6736 7.3059 8.8223 0.06615 0.48578 0.13996 1.0253 0.16431 1.1986 0.29104 2.0743 0.07488 21.552-0.25003 22.55-0.59082 1.814-1.901 2.7993-4.0537 3.0488-1.3671 0.15849-30.564 0.0701-31.846-0.0963-5.2843-0.68659-8.8934-4.001-9.7398-8.9445-0.38418-2.2439-0.62362-20.748-0.29871-23.084 0.34422-2.4746 1.7264-3.797 4.3212-4.1338 0.98769-0.12832 28.191-0.13361 30.197-6e-3"/>
|
||||
<path class="fg" d="m61.114 118.11c0.56621 0.4072 0.97816 1.0565 1.1885 1.874 0.19553 0.75909 0.24474 28.04 0.05371 29.747-0.37333 3.3361-2.6305 4.0137-5.5264 1.6584-7.9722-6.4841-9.021-7.5734-9.6795-10.052-0.26008-0.97895-0.33152-10.629-0.08811-11.876 0.38021-1.9457 1.2594-3.1946 3.7383-5.3099 7.4491-6.3564 7.5914-6.4569 9.0522-6.4008 0.70723 0.0272 0.8599 0.0709 1.2613 0.3593"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,15 +0,0 @@
|
||||
<svg width="60px" height="60px" version="1.1" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-479,-238)" stroke="#fbe468" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
|
||||
<g transform="translate(482,244)">
|
||||
<a transform="translate(0,2)">
|
||||
<polygon id="Triangle-58" points="0.15321 44 27 0 53.847 44"/>
|
||||
</a>
|
||||
<rect x="25" y="13" width="4" height="21"/>
|
||||
<a transform="translate(0,2)">
|
||||
<circle cx="27" cy="38" r="2"/>
|
||||
</a>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 750 B |
@@ -1,3 +0,0 @@
|
||||
<svg class="icon-web-globe" width="60" height="60" enable-background="new 0 0 64 64" version="1.1" viewBox="0 0 60 60" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<path class="fg" d="m48.1 14.3c-0.4-0.5-0.9-1-1.4-1.5-4.3-4.2-10.2-6.8-16.7-6.8s-12.4 2.6-16.7 6.8c-0.5 0.5-1 1-1.4 1.5-3.7 4.2-5.9 9.7-5.9 15.7s2.2 11.5 5.8 15.7c0.4 0.5 0.9 1 1.4 1.5 4.3 4.2 10.3 6.8 16.8 6.8s12.5-2.6 16.8-6.9c0.5-0.5 0.9-0.9 1.4-1.5 3.6-4.1 5.8-9.6 5.8-15.6s-2.2-11.5-5.9-15.7zm3.9 14.7h-8.1c-0.1-3-1-7.3-2.4-10.5 1.8-0.8 3.5-1.8 5.1-3 3.1 3.6 5.1 8.5 5.4 13.5zm-21-20.1c3 2.3 5.8 5.2 7.6 8.5-2.4 0.9-4.6 1.3-7.6 1.4zm-2 9.9c-3-0.1-5.2-0.6-7.6-1.4 1.8-3.3 4.6-6.3 7.6-8.5zm0 2v8.2h-10.8c0.1-3 0.9-6.8 2.3-9.8 2.7 1 5.5 1.5 8.5 1.6zm0 10.2v8c-3 0.1-5.9 0.7-8.6 1.7-1.4-3-2.1-5.7-2.3-9.7zm0 10v10.1c-3-2.3-5.8-5.3-7.7-8.6 2.4-0.9 4.7-1.4 7.7-1.5zm2 0c3 0.1 5.3 0.6 7.7 1.5-1.9 3.4-4.7 6.3-7.7 8.6zm0-2v-8h10.8c-0.1 4-0.9 6.7-2.3 9.7-2.6-1-5.5-1.5-8.5-1.7zm0-10v-8.2c3-0.1 5.8-0.7 8.5-1.6 1.4 3 2.2 6.8 2.3 9.8zm9.5-12.3c-1.7-3.2-4.1-6-6.9-8.3 4.4 0.6 8.4 2.7 11.5 5.6-1.4 1.1-3 1.9-4.6 2.7zm-14.1-8.4c-2.9 2.3-5.2 5.2-6.9 8.3-1.6-0.7-3.2-1.6-4.6-2.6 3.1-2.9 7.1-5 11.5-5.7zm-12.9 7.2c1.6 1.2 3.3 2.2 5.1 3-1.5 3.3-2.3 7.5-2.4 10.5h-8.2c0.3-5 2.3-9.9 5.5-13.5zm-5.5 15.5h8.1c0.1 4 1 7.2 2.4 10.5-1.8 0.8-3.5 1.8-5.1 3-3.2-3.7-5.1-8.5-5.4-13.5zm11.4 12.2c1.7 3.2 4.1 6.1 7 8.4-4.5-0.7-8.5-2.8-11.6-5.8 1.5-1 3-1.8 4.6-2.6zm14.2 8.5c2.9-2.4 5.3-5.2 7-8.4 1.6 0.7 3.2 1.6 4.6 2.6-3.1 3-7.2 5-11.6 5.8zm13-7.3c-1.6-1.2-3.3-2.2-5.1-3 1.4-3.2 2.3-6.5 2.4-10.5h8.1c-0.3 5.1-2.2 9.9-5.4 13.5z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,15 +0,0 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contribution">
|
||||
<header>
|
||||
<h2>Submit a contribution</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<AddContribution @contributors={{this.sortedContributors}}
|
||||
@attributes={{this.model.params}}
|
||||
@save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -1,15 +0,0 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contribution">
|
||||
<header>
|
||||
<h2>Re-submit contribution #{{this.model.id}}</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<AddContribution @attributes={{this.attributes}}
|
||||
@contributors={{this.sortedContributors}}
|
||||
@save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -1,13 +0,0 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contributor">
|
||||
<header>
|
||||
<h2>Update contributor profile</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<AddContributor @attributes={{this.attributes}} @save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -1,13 +0,0 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contributor">
|
||||
<header>
|
||||
<h2>Add contributor profile</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<AddContributor @save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
Loaded 100 of 178 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user