added typescript and started multitable widget

This commit is contained in:
Bryan Cinman 2019-01-28 03:32:45 -05:00
parent 7ec67a14d5
commit 63b6602f49
16 changed files with 4972 additions and 58 deletions

View File

@ -8,6 +8,9 @@
<script src="flipside-v1.5.4.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
padding: 0;
margin: 0;
}
@ -30,11 +33,15 @@
</head>
<body>
<div class="wrapper"><div id="widget-0" style="width: 289px;"></div></div>
<div class="wrapper"><div id="widget-1" style="width: 289px;"></div></div>
<div class="wrapper"><div id="widget-2"></div></div>
<div class="wrapper"><div id="multiTable"></div></div>
<script>
const flipside = new Flipside("<Your-API-Key");
const flipside = new Flipside("a7936778-4f87-4790-889f-3ab617271458");
flipside.createFCAS("widget-0", "nmr", {
highlights: ["BTC", "ETH", "QTUM"]
});
@ -59,6 +66,8 @@
dark: true,
borderColor: "#737e8d"
});
flipside.multiTable("multiTable", { title: { text: "Top Coins" } });
</script>
</body>
</html>

View File

@ -12,17 +12,21 @@
"license": "MIT",
"dependencies": {
"axios": "^0.18.0",
"classnames": "^2.2.6",
"lodash": "^4.17.11",
"preact": "^8.3.1"
},
"devDependencies": {
"@babel/core": "^7.1.6",
"@babel/plugin-transform-react-jsx": "^7.1.6",
"@types/classnames": "^2.2.7",
"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",
"ts-loader": "^5.3.3",
"typescript": "^3.2.4",
"url-loader": "^1.1.2",
"webpack": "^4.26.0",
"webpack-bundle-analyzer": "^3.0.3",

View File

@ -1,47 +0,0 @@
import axios from "axios";
export default class API {
constructor(apiKey) {
this.key = apiKey;
this.client = axios.create({
baseURL: "https://platform-api.flipsidecrypto.com/api/v1",
params: { api_key: apiKey }
});
}
async _fetch(url, params = {}, retryCount = 0, retryMax = 15) {
let res;
try {
res = await this.client.get(url, { params: params });
if (res.status >= 200 && res.status < 300) {
return { data: res.data, success: true };
}
} catch (e) {
console.log(
`Failed to fetch data from: "${url}". \nError message: "${e}"`
);
}
if (retryCount < retryMax) {
return await this._fetch(url, params, retryCount + 1);
}
return { data: null, success: false };
}
async fetchAssetMetric(symbol, metric, days = 7) {
const sym = `${symbol}`.toUpperCase();
return await this._fetch(`/assets/${sym}/metrics/${metric}`, {
change_over: days
});
}
async fetchAssetMetrics(symbol) {
const sym = `${symbol}`.toUpperCase();
return await this._fetch(`/assets/${sym}/metrics`);
}
async fetchFCASDistribution(asset) {
return await this._fetch(`/metrics/FCAS/assets`, {
visual_distribution: true
});
}
}

62
src/api.ts Normal file
View File

@ -0,0 +1,62 @@
import axios, { AxiosInstance, AxiosPromise } from "axios";
export default class API {
key: string;
client: AxiosInstance;
constructor(apiKey: string) {
this.key = apiKey;
this.client = axios.create({
baseURL: "https://platform-api.flipsidecrypto.com/api/v1",
params: { api_key: apiKey }
});
}
async _fetch(
method: string,
url: string,
params = {},
retryCount = 0,
retryMax = 15
): Promise<any> {
let res;
try {
res = await this.client.request({ url, method, params: params });
if (res.status >= 200 && res.status < 300) {
return { data: res.data, success: true };
}
} catch (e) {
console.log(
`Failed to fetch data from: "${url}". \nError message: "${e}"`
);
}
if (retryCount < retryMax) {
return await this._fetch("GET", url, params, retryCount + 1);
}
return { data: null, success: false };
}
async fetchAssetMetric(symbol: string, metric: string, days = 7) {
const sym = `${symbol}`.toUpperCase();
return await this._fetch("GET", `/assets/${sym}/metrics/${metric}`, {
change_over: days
});
}
async fetchAssetMetrics(symbol: string) {
const sym = `${symbol}`.toUpperCase();
return await this._fetch("GET", `/assets/${sym}/metrics`);
}
async fetchFCASDistribution(asset: string) {
return await this._fetch("GET", `/metrics/FCAS/assets`, {
visual_distribution: true
});
}
async fetchMetrics(assets: string[]) {
return await this.client.post(`/assets/metrics`, {
metrics: ["fcas", "dev"]
});
}
}

View File

View File

4
src/custom.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "*.css" {
const css: any;
export default css;
}

View File

@ -2,8 +2,22 @@ import { h, Component } from "preact";
import Score from "./score";
import Plot from "./plot";
import "./styles.scss";
import API from "../api";
type Props = {
symbol: string;
api: API;
opts: any;
};
type State = {
metric: any;
loading: boolean;
};
export default class FCAS extends Component<Props, State> {
interval: number;
export default class FCAS extends Component {
constructor() {
super();
this.state = { loading: true, metric: null };
@ -58,7 +72,7 @@ export default class FCAS extends Component {
this._update();
}
render({ opts, api, symbol }, { metric, loading }) {
render({ opts, api, symbol }: Props, { metric, loading }: State) {
if (loading) return null;
return (
<div class="fs-container">

View File

@ -2,13 +2,23 @@ import { h, render } from "preact";
import FCAS from "./fcas";
import API from "./api";
import Table from "./table";
import MultiTable, { Props as MultiTableProps } from "./multiTable";
class Flipside {
constructor(apiKey) {
type MultiTableOpts = {};
export default class Flipside {
api: API;
constructor(apiKey: string) {
this.api = new API(apiKey);
}
createTable(el, symbol, opts) {
multiTable(el: string, opts: MultiTableProps) {
const element = document.getElementById(el);
render(<MultiTable {...opts} api={this.api} />, element);
}
createTable(el: string, symbol: string, opts: object) {
const defaults = {
dark: false
};
@ -18,7 +28,7 @@ class Flipside {
render(<Table symbol={symbol} api={this.api} {...mergedOpts} />, element);
}
createFCAS(el, symbol, opts) {
createFCAS(el: string, symbol: string, opts: object) {
symbol = symbol.toLowerCase();
const defaults = {
score: true,
@ -38,4 +48,10 @@ class Flipside {
}
}
declare global {
interface Window {
Flipside: any;
}
}
window.Flipside = Flipside;

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="8px" height="6px" viewBox="0 0 8 6" 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>Triangle</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Table-Widget" transform="translate(-405.000000, -268.000000)" fill="#000000">
<g id="Group-21" transform="translate(404.670464, 266.000000)">
<polygon id="Triangle" transform="translate(4.329536, 5.000000) rotate(-180.000000) translate(-4.329536, -5.000000) " points="4.32953555 2 8.32953555 8 0.329535553 8"></polygon>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 820 B

192
src/multiTable/index.tsx Normal file
View File

@ -0,0 +1,192 @@
import { h, Component } from "preact";
import classnames from "classnames";
import API from "../api";
import "./style.scss";
// Define the columns, the content of their header, and how their data is rendered.
type ColumnDefinition = {
header: string;
renderItem: (row: Row) => any;
sortable?: boolean;
};
const COLUMNS: { [k: string]: ColumnDefinition } = {
coin: {
header: "Coin",
renderItem: (row: Row) => row.symbol
},
fcas: {
header: "FCAS",
renderItem: (row: Row) => row.fcas,
sortable: true
},
trend: {
header: "7D",
renderItem: (row: Row) => row.change_over
},
userActivity: {
header: "User Activity",
renderItem: (_row: Row) => "usr_act",
sortable: true
},
developerBehavior: {
header: "Developer Behavior",
renderItem: (row: Row) => row.dev,
sortable: true
},
marketMaturity: {
header: "Market Maturity",
renderItem: (row: Row) => row.utility,
sortable: true
},
rank: {
header: "Rank",
renderItem: (row: Row) => row.fcas,
sortable: true
}
};
type ColumnName = "trend" | "developerBehavior" | "marketMaturity" | "rank";
export type Props = {
mode?: "light" | "dark";
assets?: string | string[];
autoWidth?: boolean;
limit?: number;
columns?: ColumnName[];
title?: {
text: string;
style?: object;
};
trends?: {
enabled: boolean;
};
headers?: {
color?: string;
style?: object;
};
rows?: {
alternating?: boolean;
alternatingColors?: string[];
dividers?: boolean;
dividersColor?: string;
style?: object;
};
api?: API;
};
type Row = {
symbol: string;
fcas: number;
dev: number;
utility: number;
change_over: number;
};
type State = {
loading: boolean;
sortKey: string;
sortOrder: "asc" | "desc";
rows?: Row[];
};
export default class MultiTable extends Component<Props, State> {
constructor() {
super();
this.state = {
loading: true,
sortKey: "rank",
sortOrder: "asc"
};
}
static defaultProps = {
mode: "light",
columns: [
"trend",
"userActivity",
"developerBehavior",
"marketMaturity",
"rank"
],
rows: {
alternating: true
}
};
async componentDidMount() {
this._getData();
}
async _getData() {
const res = await this.props.api.fetchMetrics(["btc", "eth"]);
this.setState({
loading: false,
rows: res.data
});
}
handleSort(col: string) {
if (COLUMNS[col].sortable) {
this.setState({ sortKey: col });
}
}
render(props: Props, state: State) {
if (state.loading) return null;
const columns = ["coin", "fcas", ...props.columns];
const classes = classnames("fs-multi", `fs-multi-${props.mode}`, {
"fs-multi-alternating": props.rows.alternating,
"fs-multi-dividers": props.rows.dividers
});
return (
<div class={classes}>
<header>
{props.title && <h1 style={props.title.style}>{props.title.text}</h1>}
<p>
Powered by <a href="#">Flipside Crypto</a>
</p>
<a href="#">What's FCAS?</a>
</header>
<table>
<thead>
<tr>
{columns.map(col => {
const column = COLUMNS[col];
const classes = classnames(`fs-multi-${col}`, {
"fs-multi-sortable": column.sortable
});
return (
<th class={classes} onClick={() => this.handleSort(col)}>
{state.sortKey === col && <span class={`fs-multi-caret`} />}
{column.header}
</th>
);
})}
</tr>
</thead>
<tbody>
{state.rows.map(asset => (
<tr>
{columns.map(col => (
<td class={`fs-multi-${col}`}>
{COLUMNS[col].renderItem(asset)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
}

67
src/multiTable/style.scss Normal file
View File

@ -0,0 +1,67 @@
.fs-multi {
table {
text-align: right;
border-collapse: collapse;
font-size: 14px;
}
th,
td {
padding: 5px 10px;
}
th {
font-weight: 400;
vertical-align: bottom;
}
td {
font-weight: 500;
}
&-light {
td.fs-multi-fcas,
td.fs-multi-coin {
color: #2d57ed;
}
}
&-dark {
td.fs-multi-fcas,
td.fs-multi-coin {
color: #2d57ed;
}
}
&-alternating {
tbody tr:nth-child(2n-1) {
background-color: #eeeeee;
}
}
&-dividers {
td {
border-bottom: 1px solid #979797;
}
}
&-coin {
text-align: left;
}
// sorted column icon
&-caret {
display: inline-block;
background: url(./images/caret.svg);
width: 8px;
height: 6px;
}
// sortable columns headers
&-sortable {
cursor: pointer;
&:hover {
background: #eee;
}
}
}

12
tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"sourceMap": true,
"module": "commonjs",
"target": "es6",
"jsx": "react",
"jsxFactory": "h",
"allowJs": true
}
}

View File

@ -3,15 +3,20 @@ let version = require("./package.json").version;
let filename = `flipside-v${version}.js`;
module.exports = {
entry: "./src/index.js",
entry: "./src/index.tsx",
output: {
filename: filename,
path: path.resolve(__dirname, "dist")
},
mode: "development",
devtool: "inline-source-map",
resolve: {
extensions: [".tsx", ".ts", ".js"]
},
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" },
{ test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ },
{ test: /\.js$/, loader: "babel-loader", exclude: /node_modules/ },
{ test: /\.scss$/, use: ["style-loader", "css-loader", "sass-loader"] },
{ test: /\.svg$/, use: { loader: "url-loader" } }
]

4533
yarn-error.log Normal file

File diff suppressed because it is too large Load Diff

View File

@ -132,6 +132,10 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
"@types/classnames@^2.2.7":
version "2.2.7"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.7.tgz#fb68cc9be8487e6ea5b13700e759bfbab7e0fefd"
"@webassemblyjs/ast@1.7.11":
version "1.7.11"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace"
@ -749,6 +753,14 @@ chalk@^2.0.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^2.3.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
check-types@^7.3.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.4.0.tgz#0378ec1b9616ec71f774931a3c6516fad8c152f4"
@ -798,6 +810,10 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@ -1269,7 +1285,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
enhanced-resolve@^4.1.0:
enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
dependencies:
@ -3492,7 +3508,7 @@ selfsigned@^1.9.1:
dependencies:
node-forge "0.7.5"
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
@ -3999,6 +4015,16 @@ tryer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
ts-loader@^5.3.3:
version "5.3.3"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-5.3.3.tgz#8b4af042e773132d86b3c99ef0acf3b4d325f473"
dependencies:
chalk "^2.3.0"
enhanced-resolve "^4.0.0"
loader-utils "^1.0.2"
micromatch "^3.1.4"
semver "^5.0.1"
tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
@ -4028,6 +4054,10 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
typescript@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d"
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"