First commit.

This commit is contained in:
Jim Myers 2018-11-30 16:53:43 -05:00
commit dbc799602f
21 changed files with 12344 additions and 0 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"plugins": [["@babel/plugin-transform-react-jsx", { "pragma": "h" }]]
}

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.vscode/
.DS_STORE
node_modules/

9
License.md Normal file
View File

@ -0,0 +1,9 @@
(The MIT License)
Copyright (c) 2018 Flipsidecrypto.com hello@flipsidecrypto.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

64
README.md Normal file
View File

@ -0,0 +1,64 @@
# FlipsideJS
FlipsideJS provides a library embeddable widgets that display data from the Flipside Platform API.
## Install
```html
<script src="https://flipsidecrypto.com/flipside-js/flipside.js"></script>
```
## Usage
```html
<div id="container"></div>
<script>
var flipside = new Flipside(YOUR_FLIPSIDE_API_KEY);
flipside.create("container", "BTC");
</script>
```
## API
_create(id: string, symbol: string, options: object)_
Creates an FCAS widget in the given element ID.
#### Parameters
- _id_: ID of the element in which to create the widget.
- _symbol_: Symbol of the asset. e.g "BTC"
- _opts_: Display options for the widget
#### Options
- `score: boolean`: Show/hide the score section.
- `plot: boolean`: Show/hide the plot graph.
- `logo: boolean`: Show/hide the asset's logo.
- `symbol: boolean`: Show/hide the asset's symbol.
- `trend: boolean`: Show/hide the 7 day trend.
- `rank: boolean`: Show/hide the FCAS rank.
#### Default options
```js
flipside.create("container", "ETH", {
score: true,
plot: true,
logo: true,
symbol: true,
trend: true,
rank: true
});
```
## Building
```
$ npm run build
```
## Development
```
$ npm run dev
```

8
dist/flipside.js vendored Normal file

File diff suppressed because one or more lines are too long

24
index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>flipside.js</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="flipside.js"></script>
<style>
.wrapper {
display: flex;
width: 100%;
flex-wrap: wrap;
}
</style>
</head>
<body>
<div class="wrapper"><div id="widget-0"></div></div>
<script>
const flipside = new Flipside("<YourApiKey>");
flipside.create("widget-0", "btc");
</script>
</body>
</html>

7209
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "flipside-js",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack -p --env production",
"build:stats": "webpack --env production --json > stats.json"
},
"author": "Flipsidecrypto.com <hello@flipsidecrypto.com>",
"license": "MIT",
"dependencies": {
"axios": "^0.18.0",
"preact": "^8.3.1"
},
"devDependencies": {
"@babel/core": "^7.1.6",
"@babel/plugin-transform-react-jsx": "^7.1.6",
"babel-loader": "^8.0.4",
"css-loader": "^1.0.1",
"node-sass": "^4.10.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^4.26.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}

25
src/api.js Normal file
View File

@ -0,0 +1,25 @@
import axios from "axios";
export default class API {
constructor(apiKey) {
this.client = axios.create({
baseURL: "https://platform-api.flipsidecrypto.com/api/v1",
params: { api_key: apiKey }
});
}
async fetchAssetMetric(symbol, metric = "FCAS", days = 7) {
const sym = `${symbol}`.toUpperCase();
const res = await this.client.get(`/assets/${sym}/metrics/${metric}`, {
params: { change_over: days }
});
return res.data;
}
async fetchFCASDistribution(asset) {
const res = await this.client.get(`/metrics/FCAS/assets`, {
params: { visual_distribution: true }
});
return res.data;
}
}

41
src/fcas/index.js Normal file
View File

@ -0,0 +1,41 @@
import { h, Component } from "preact";
import Score from "./score";
import Plot from "./plot";
import "./styles.scss";
export default class FCAS extends Component {
constructor() {
super();
this.state = {
loading: true,
metric: null
};
}
async componentDidMount() {
const data = await this.props.api.fetchAssetMetric(
this.props.symbol,
"FCAS"
);
this.setState({
loading: false,
metric: {
fcas: Math.round(data.value),
change: data.percent_change,
name: data.asset_name
}
});
}
render({ opts, api, symbol }, { metric, loading }) {
if (loading) return null;
return (
<div class="fs-container">
{opts.score && <Score symbol={symbol} metric={metric} opts={opts} />}
{opts.plot && (
<Plot symbol={symbol} metric={metric} api={api} opts={opts} />
)}
</div>
);
}
}

119
src/fcas/plot/index.js Normal file
View File

@ -0,0 +1,119 @@
import { h, Component } from "preact";
import "./styles.scss";
const PLOT_WIDTH = 240;
const PLOT_SCALE = PLOT_WIDTH / 1000;
export default class Plot extends Component {
constructor() {
super();
this.state = {
loading: true,
distribution: null
};
}
async componentDidMount() {
const data = await this.props.api.fetchFCASDistribution();
this.setState({
loading: false,
distribution: data
});
}
render({ metric, symbol }, { loading, distribution }) {
if (loading) return null;
const xPos = PLOT_SCALE * metric.fcas;
const highlightedAssets = distribution
.filter(i => ["ETH", "BTC"].indexOf(i.symbol) > -1)
.filter(i => i.symbol != symbol.toUpperCase());
let lastHighlightX, lastHighlightY;
return (
<svg width={PLOT_WIDTH} height="120" overflow="visible">
<defs>
<linearGradient id="gradient">
<stop stop-color="#ff2600" offset="0%" />
<stop stop-color="#ff7a18" offset="40%" />
<stop stop-color="#8fcb89" offset="68%" />
<stop stop-color="#30a92d" offset="92%" />
</linearGradient>
</defs>
<g>
<circle cx="0" cy="44" r="2.5" />
<text x="6" y="47" font-size="8">
Coins
</text>
</g>
<g fill="rgba(0, 0, 0, 0.4)">
{distribution.map(i => (
<circle cx={PLOT_SCALE * i.value} cy="58" r="2.5" />
))}
</g>
<rect
x="0"
y="64"
width={PLOT_WIDTH}
height="8"
fill="url(#gradient)"
/>
<text y="85" text-anchor="middle" class="fs-plot__x">
<tspan x="0">0</tspan>
<tspan x="50%">500</tspan>
<tspan x="100%">1000</tspan>
</text>
<text class="fs-plot__blue" text-anchor="middle">
<tspan x={xPos} y="14">
{symbol.toUpperCase()}
</tspan>
<tspan x={xPos} y="104">
{metric.fcas}
</tspan>
</text>
<line
x1={xPos}
y1="16"
x2={xPos}
y2="92"
style="stroke:rgb(45,87,237); stroke-width:1"
/>
{highlightedAssets.map(a => {
let yOffset = 0;
const xPos = PLOT_SCALE * a.value;
if (lastHighlightX && xPos - lastHighlightX < 10) {
yOffset = 10;
}
lastHighlightX = xPos;
return (
<g>
<text
x={xPos}
y={44 - yOffset}
text-anchor="middle"
font-size="10"
>
{a.symbol}
</text>
<line
x1={xPos}
y1={47 - yOffset}
x2={xPos}
y2="60"
style="stroke:rgb(0,0,0); stroke-width:0.5"
/>
</g>
);
})}
</svg>
);
}
}

View File

@ -0,0 +1,8 @@
.fs-plot__blue {
fill: #2d57ed;
font-size: 12px;
}
.fs-plot__x {
font-size: 7px;
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="11px" height="12px" viewBox="0 0 11 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.4 (67378) - http://www.bohemiancoding.com/sketch -->
<title>icon-down</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M4,7 L0,7 L5.5,0 L11,7 L7,7 L7,12 L4,12 L4,7 Z" id="icon-down" fill="#FF2700" transform="translate(5.500000, 6.000000) rotate(-180.000000) translate(-5.500000, -6.000000) "></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 630 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="8px" height="7px" viewBox="0 0 8 7" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.4 (67378) - http://www.bohemiancoding.com/sketch -->
<title>icon-eq</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M0,4 L8,4 L8,7 L0,7 L0,4 Z M0,0 L8,0 L8,3 L0,3 L0,0 Z" id="icon-eq" fill="#808080"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 534 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="11px" height="12px" viewBox="0 0 11 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.4 (67378) - http://www.bohemiancoding.com/sketch -->
<title>icon-up</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M4,7 L0,7 L5.5,0 L11,7 L7,7 L7,12 L4,12 L4,7 Z" id="icon-up" fill="#057511"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 531 B

126
src/fcas/score/index.js Normal file
View File

@ -0,0 +1,126 @@
import { h, Component } from "preact";
import "./styles.scss";
function round(value) {
if (!value) {
return value;
}
return Math.round(value * 100) / 100;
}
export default class Score extends Component {
constructor() {
super();
this.state = {
showTooltip: false
};
this._showTooltip = this._showTooltip.bind(this);
this._hideTooltip = this._hideTooltip.bind(this);
}
_showTooltip() {
this.setState({ showTooltip: true });
}
_hideTooltip() {
this.setState({ showTooltip: false });
}
render({ opts, symbol, metric }, { showTooltip }) {
let rank;
if (metric.fcas <= 500) {
rank = "f";
} else if (metric.fcas <= 649) {
rank = "c";
} else if (metric.fcas <= 749) {
rank = "b";
} else if (metric.fcas <= 899) {
rank = "a";
} else {
rank = "s";
}
let trendDir, trendIcon;
if (metric.change < 0) {
trendDir = "down";
trendIcon = require("./images/icon-down.svg");
} else if (metric.change == 0) {
trendDir = "eq";
trendIcon = require("./images/icon-eq.svg");
} else {
trendDir = "up";
trendIcon = require("./images/icon-up.svg");
}
const trendDiff = Math.abs(metric.fcas * (metric.change / 100));
return (
<div class="fs-score">
<header class="fs-token">
<img
class="fs-token__logo"
src={`https://s3.amazonaws.com/fsc-crypto-icons/svg/color/${symbol}.svg`}
/>
<h1 class="fs-token__name">
{metric.name}
{opts.symbol && <span class="fs-token__sym">{symbol}</span>}
</h1>
</header>
<div class="fs-fcas">
<h2 class="fs-fcas__header">FCAS</h2>
<span
class="fs-link fs-fcas__what"
onMouseEnter={this._showTooltip}
onMouseLeave={this._hideTooltip}
>
What's this?
{showTooltip && (
<div class="fs-tooltip">
<div class="fs-tooltip__content">
<p>
<b>FCAS</b> is Flipsides Crypto Asset Score, ranging from 0
- 1000. The score combines values from the 3 major market
factors to create a comparative metric across digital
assets.
</p>
<p>
Powered by{" "}
<a
target="_blank"
href="https://flipsidecrypto.com"
class="fs-link"
>
flipsidecrypto.com
</a>
</p>
</div>
</div>
)}
</span>
</div>
<div class="fs-data">
<h3 class="fs-value">{metric.fcas}</h3>
{opts.trend && (
<div class="fs-trend">
<div class={`fs-trend__change fs-trend__change--${trendDir}`}>
<img class="fs-trend__icon" src={trendIcon} />{" "}
{round(trendDiff)}
</div>
7d
</div>
)}
{opts.rank && (
<div class="fs-rank">
<b class={`fs-rank__letter fs-rank__letter--${rank}`}>{rank}</b>{" "}
Rank
</div>
)}
</div>
</div>
);
}
}

148
src/fcas/score/styles.scss Normal file
View File

@ -0,0 +1,148 @@
.fs-score {
margin-bottom: 25px;
}
.fs-token {
display: flex;
align-items: center;
margin-bottom: 26px;
}
.fs-token__logo {
margin-right: 8px;
height: 24px;
width: 24px;
}
.fs-token__name {
font-size: 20px;
font-weight: 400;
margin: 0;
}
.fs-token__sym {
font-size: 12px;
color: #2d57ed;
text-transform: uppercase;
margin-left: 6px;
}
.fs-fcas {
display: flex;
align-items: center;
}
.fs-fcas__header {
margin: 0 10px 0 0;
font-size: 18px;
font-weight: 800;
}
.fs-fcas__what {
font-size: 8px;
position: relative;
}
.fs-tooltip {
padding-left: 15px;
position: absolute;
top: 50%;
left: 100%;
transform: translateY(-50%);
}
.fs-tooltip__content {
position: relative;
width: 200px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.15);
font-size: 12px;
color: #000;
line-height: 14px;
padding: 5px 15px;
border: 1px solid rgba(200, 200, 200, 0.5);
&:before {
display: block;
content: "";
width: 20px;
height: 20px;
position: absolute;
left: -10px;
top: calc(50% - 10px);
transform: rotate(-45deg);
background: #fff;
box-shadow: -1px -1px 0 rgba(200, 200, 200, 0.5);
}
}
.fs-data {
display: flex;
align-items: center;
text-align: center;
font-size: 10px;
}
.fs-value {
font-size: 58px;
font-weight: bold;
margin: 0 14px 0 0;
}
.fs-trend {
margin-right: 16px;
}
.fs-trend__icon {
margin-right: 2px;
}
.fs-trend__change {
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
height: 28px;
margin-bottom: 5px;
&--up {
color: #057511;
}
&--down {
color: #ff2700;
}
&--eq {
color: #808080;
}
}
.fs-rank {
text-align: center;
font-size: 10px;
}
.fs-rank__letter {
width: 32px;
height: 28px;
display: block;
font-size: 18px;
font-weight: 400;
border-radius: 2px;
line-height: 28px;
margin-bottom: 5px;
text-transform: uppercase;
}
.fs-rank__letter--s {
background: #68ba66;
}
.fs-rank__letter--a {
background: #8fcb89;
}
.fs-rank__letter--b {
background: #b2dbad;
}
.fs-rank__letter--c {
background: #ff7a18;
}
.fs-rank__letter--f {
background: #ff2600;
}

13
src/fcas/styles.scss Normal file
View File

@ -0,0 +1,13 @@
.fs-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
padding: 28px 40px 40px;
line-height: 1;
border-radius: 25px;
}
.fs-link {
color: #2d57ed;
text-decoration: underline;
cursor: pointer;
}

27
src/index.js Normal file
View File

@ -0,0 +1,27 @@
import { h, render } from "preact";
import FCAS from "./fcas";
import API from "./api";
class Flipside {
constructor(apiKey) {
this.api = new API(apiKey);
}
create(id, symbol, opts) {
const defaults = {
score: true,
plot: true,
symbol: true,
logo: true,
trend: true,
rank: true
};
const mergedOpts = Object.assign({}, defaults, opts);
render(
<FCAS symbol={symbol} api={this.api} opts={mergedOpts} />,
document.getElementById(id)
);
}
}
window.Flipside = Flipside;

18
webpack.config.js Normal file
View File

@ -0,0 +1,18 @@
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "flipside.js",
path: path.resolve(__dirname, "dist")
},
mode: "development",
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" },
{ test: /\.scss$/, use: ["style-loader", "css-loader", "sass-loader"] },
{ test: /\.svg$/, use: { loader: "url-loader" } }
]
}
};

4441
yarn.lock Normal file

File diff suppressed because it is too large Load Diff