mirror of
https://github.com/FlipsideCrypto/flipside-js.git
synced 2026-02-06 10:48:11 +00:00
Merge branch 'bugfix/map-multitable-columns' into asset-ids
This commit is contained in:
commit
eab5a73255
68
index.html
68
index.html
@ -23,7 +23,7 @@
|
||||
.wrapper {
|
||||
border: 1px solid #ccc;
|
||||
padding: 20px;
|
||||
width: 500px;
|
||||
width: 600px;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
}
|
||||
@ -61,21 +61,25 @@
|
||||
</div>
|
||||
|
||||
<div class="wrapper"><div id="multiTable"></div></div>
|
||||
<div class="wrapper">
|
||||
<div id="multiTableFcas"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const flipside = new Flipside("a7936778-4f87-4790-889f-3ab617271458");
|
||||
|
||||
flipside.chart("chart", {
|
||||
title: "Bitcoin Health",
|
||||
title: "Flipside 25 - Overall Industry Health",
|
||||
series: [
|
||||
{
|
||||
name: "BTC FCAS",
|
||||
asset_id: 00303043,
|
||||
name: "FLIP25",
|
||||
symbol: "FLIP25",
|
||||
metric: "fcas",
|
||||
type: "line",
|
||||
yAxis: 1
|
||||
}
|
||||
],
|
||||
colors: ["#81A0F1"],
|
||||
rangeSelector: {
|
||||
enabled: true
|
||||
}
|
||||
@ -136,23 +140,65 @@
|
||||
borderColor: "#737e8d"
|
||||
});
|
||||
|
||||
// Price table
|
||||
flipside.multiTable("multiTable", {
|
||||
assets: ["btc", "etc"],
|
||||
columns: ["rank", "trend"],
|
||||
widgetType: "price-multi-table",
|
||||
columns: ["market_cap", "price", "volume_24h"],
|
||||
sortBy: "market_cap",
|
||||
showFullName: true,
|
||||
fontFamily: "inherit",
|
||||
limit: 50,
|
||||
mode: "light",
|
||||
headers: {
|
||||
style: {
|
||||
fontWeight: "bold",
|
||||
padding: "10px 0 10px 0"
|
||||
}
|
||||
},
|
||||
rows: {
|
||||
dividers: true,
|
||||
dividersColor: "#eaeaea",
|
||||
alternating: false,
|
||||
style: {
|
||||
padding: "10px 0 10px 0"
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: "Top 50 Coins By Market Cap",
|
||||
style: { fontSize: "24px", fontWeight: 400 }
|
||||
}
|
||||
});
|
||||
|
||||
// FCAS
|
||||
flipside.multiTable("multiTableFcas", {
|
||||
autoWidth: false,
|
||||
assets: null,
|
||||
columns: [
|
||||
"fcas",
|
||||
"trend",
|
||||
"userActivity",
|
||||
"developerBehavior",
|
||||
"marketMaturity",
|
||||
"rank"
|
||||
],
|
||||
exlusions: null,
|
||||
fontFamily: "inherit",
|
||||
headers: {},
|
||||
limit: 15,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
mode: "light",
|
||||
rows: {
|
||||
dividers: true
|
||||
alternating: true,
|
||||
alternatingColors: "#eeeeee",
|
||||
dividers: false
|
||||
},
|
||||
title: {
|
||||
text: "Top Coins",
|
||||
style: { fontSize: "24px", fontWeight: 400 }
|
||||
style: {}
|
||||
},
|
||||
trend: {
|
||||
changeOver: 7
|
||||
enabled: true,
|
||||
changeOver: 7,
|
||||
style: {}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "FlipsideJS provides a library embeddable widgets that display data from the Flipside Platform API, including FCAS.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"start": "NODE_ENV=development webpack-dev-server",
|
||||
"build": "webpack -p --env production",
|
||||
"build:stats": "webpack -p --env production --json > stats.json"
|
||||
},
|
||||
|
||||
13
src/api.ts
13
src/api.ts
@ -59,14 +59,14 @@ export default class API {
|
||||
}
|
||||
|
||||
async fetchMetrics(payload: {
|
||||
assets: string[];
|
||||
exclusions: string[];
|
||||
assets?: string[];
|
||||
exclusions?: string[];
|
||||
sort_by?: string;
|
||||
sort_desc?: boolean;
|
||||
page: number;
|
||||
page?: number;
|
||||
size?: number;
|
||||
metrics: string[];
|
||||
change_over: number;
|
||||
metrics?: string[];
|
||||
change_over?: number;
|
||||
}) {
|
||||
return await this.client.post(`/assets/metrics`, payload);
|
||||
}
|
||||
@ -97,7 +97,8 @@ export type WidgetLinksSlug =
|
||||
| "multi-table"
|
||||
| "table"
|
||||
| "score"
|
||||
| "chart";
|
||||
| "chart"
|
||||
| "price-multi-table";
|
||||
export type WidgetLinksLink = {
|
||||
widget_id: string;
|
||||
name: string;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { h, Component } from "preact";
|
||||
import Highcharts from "highcharts/highstock";
|
||||
import merge from "lodash/merge";
|
||||
import API from "../api";
|
||||
import { createApiSeries, createSeries } from "./helpers";
|
||||
@ -9,7 +8,13 @@ import CustomLinks from "../components/customLinks";
|
||||
import * as css from "./style.css";
|
||||
import NoDataMessage from "../components/noDataMessage";
|
||||
|
||||
require("highcharts/modules/exporting")(Highcharts);
|
||||
let Highcharts: any;
|
||||
if (!window.Highcharts) {
|
||||
Highcharts = require("highcharts/highstock");
|
||||
require("highcharts/modules/exporting")(Highcharts);
|
||||
} else {
|
||||
Highcharts = window.Highcharts;
|
||||
}
|
||||
|
||||
type ChartType = "line" | "bar";
|
||||
type ChartAxis = "left" | "right";
|
||||
|
||||
@ -5,7 +5,13 @@ import classNames from "classnames";
|
||||
import * as css from "./style.css";
|
||||
|
||||
type Props = {
|
||||
widget: "spectrum" | "multi-table" | "table" | "score" | "chart";
|
||||
widget:
|
||||
| "spectrum"
|
||||
| "multi-table"
|
||||
| "table"
|
||||
| "score"
|
||||
| "chart"
|
||||
| "price-multi-table";
|
||||
api: API;
|
||||
style?: any;
|
||||
linkClass?: string;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-wrap: wrap;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.link {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
4
src/custom.d.ts
vendored
4
src/custom.d.ts
vendored
@ -2,3 +2,7 @@ declare module "*.css" {
|
||||
const css: any;
|
||||
export default css;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
Highcharts: any;
|
||||
}
|
||||
|
||||
@ -4,75 +4,105 @@ import Rank from "../components/rank";
|
||||
import Trend from "../components/trend";
|
||||
import CustomLinks from "../components/customLinks";
|
||||
import API from "../api";
|
||||
import { Row, ColumnName, ColumnDefinition } from "./types";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import intersection from "lodash/intersection";
|
||||
import reverse from "lodash/reverse";
|
||||
import "./style.scss";
|
||||
|
||||
import sortBy from "lodash/sortBy";
|
||||
import reverse from "lodash/reverse";
|
||||
|
||||
// Define the columns, the content of their header, and how their data is rendered.
|
||||
type ColumnDefinition = {
|
||||
header: string;
|
||||
renderItem: (row: Row) => any;
|
||||
sortKey?: string;
|
||||
};
|
||||
function mapConfigColumnsNames(columns: string[]): string[] {
|
||||
const actualColumnNames: { [k: string]: string } = {
|
||||
marketMaturity: "market-maturity",
|
||||
userActivity: "utility",
|
||||
developerBehavior: "dev"
|
||||
};
|
||||
return columns.map(col => actualColumnNames[col] || col);
|
||||
}
|
||||
|
||||
const COLUMNS: { [k: string]: ColumnDefinition } = {
|
||||
coin: {
|
||||
symbol: {
|
||||
header: "Coin",
|
||||
renderItem: (row: Row) => row.symbol
|
||||
renderItem: row => row.symbol
|
||||
},
|
||||
name: {
|
||||
header: "Coin",
|
||||
renderItem: row => row.asset_name || row.symbol
|
||||
},
|
||||
|
||||
fcas: {
|
||||
header: "FCAS",
|
||||
renderItem: (row: Row) => row.fcas,
|
||||
renderItem: row => row.fcas,
|
||||
sortKey: "fcas"
|
||||
},
|
||||
|
||||
trend: {
|
||||
header: "7D",
|
||||
renderItem: (row: Row) => (
|
||||
<Trend change={row.fcas_change} value={row.fcas} />
|
||||
),
|
||||
renderItem: row => <Trend change={row.fcas_change} value={row.fcas} />,
|
||||
sortKey: "fcas_change"
|
||||
},
|
||||
|
||||
userActivity: {
|
||||
header: "User Activity",
|
||||
renderItem: (row: Row) => row.utility,
|
||||
renderItem: row => row.utility,
|
||||
sortKey: "utility"
|
||||
},
|
||||
|
||||
developerBehavior: {
|
||||
header: "Developer Behavior",
|
||||
renderItem: (row: Row) => row.dev,
|
||||
renderItem: row => row.dev,
|
||||
sortKey: "dev"
|
||||
},
|
||||
|
||||
marketMaturity: {
|
||||
header: "Market Maturity",
|
||||
renderItem: (row: Row) => row.market_maturity,
|
||||
renderItem: row => row.market_maturity,
|
||||
sortKey: "market_maturity"
|
||||
},
|
||||
|
||||
rank: {
|
||||
header: "Rank",
|
||||
renderItem: (row: Row) => <Rank score={row.fcas} />,
|
||||
renderItem: row => <Rank score={row.fcas} />,
|
||||
sortKey: "fcas"
|
||||
},
|
||||
volume_24h: {
|
||||
header: "Volume",
|
||||
renderItem: row =>
|
||||
`$${row.volume_24h.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 0
|
||||
})}`,
|
||||
sortKey: "volume_24h"
|
||||
},
|
||||
market_cap: {
|
||||
header: "Market Cap",
|
||||
renderItem: row =>
|
||||
`$${row.market_cap.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 0
|
||||
})}`,
|
||||
sortKey: "market_cap"
|
||||
},
|
||||
price: {
|
||||
header: "Price",
|
||||
renderItem: row => {
|
||||
let price: any = row.price;
|
||||
if (!price) return "NA";
|
||||
let value: number = parseFloat(parseFloat(price).toFixed(2));
|
||||
if (value === 0.0) {
|
||||
value = parseFloat(parseFloat(price).toFixed(4));
|
||||
}
|
||||
return `$${value}`;
|
||||
},
|
||||
sortKey: "price"
|
||||
}
|
||||
};
|
||||
|
||||
type ColumnName =
|
||||
| "trend"
|
||||
| "developerBehavior"
|
||||
| "userActivity"
|
||||
| "marketMaturity"
|
||||
| "rank";
|
||||
|
||||
export type Props = {
|
||||
widgetType?:
|
||||
| "spectrum"
|
||||
| "multi-table"
|
||||
| "table"
|
||||
| "score"
|
||||
| "chart"
|
||||
| "price-multi-table";
|
||||
mode?: "light" | "dark";
|
||||
assets?: string[];
|
||||
showFullName?: boolean;
|
||||
exclusions?: string[];
|
||||
autoWidth?: boolean;
|
||||
sortBy?: ColumnName;
|
||||
limit?: number;
|
||||
page?: number;
|
||||
columns?: ColumnName[];
|
||||
@ -93,36 +123,52 @@ export type Props = {
|
||||
dividers?: boolean;
|
||||
dividersColor?: string;
|
||||
style?: object;
|
||||
padding?: string;
|
||||
headerBold?: boolean;
|
||||
};
|
||||
api?: API;
|
||||
};
|
||||
|
||||
type Row = {
|
||||
symbol: string;
|
||||
fcas: number;
|
||||
dev: number;
|
||||
utility: number;
|
||||
fcas_change: number;
|
||||
dev_change: number;
|
||||
utility_change: number;
|
||||
market_maturity: number;
|
||||
market_maturity_change: number;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loading: boolean;
|
||||
priceFilterRequired: boolean;
|
||||
filteredColumns: ColumnName[];
|
||||
pageSortBy: ColumnName;
|
||||
sortColumn: string;
|
||||
sortOrder: "asc" | "desc";
|
||||
rows?: Row[];
|
||||
};
|
||||
|
||||
export default class MultiTable extends Component<Props, State> {
|
||||
constructor() {
|
||||
super();
|
||||
updateInterval: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// if price, market_cap, or volume_24h are included in columns then remove all other columns
|
||||
let filteredColumns = props.columns;
|
||||
let priceFilterRequired = false;
|
||||
const includedMarketCapColumns = intersection(filteredColumns, [
|
||||
"price",
|
||||
"market_cap",
|
||||
"volume_24h"
|
||||
]) as ColumnName[];
|
||||
if (includedMarketCapColumns.length > 0) {
|
||||
filteredColumns = includedMarketCapColumns;
|
||||
priceFilterRequired = true;
|
||||
} else {
|
||||
if (filteredColumns.indexOf("fcas") === -1) {
|
||||
filteredColumns = ["fcas", ...filteredColumns];
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
loading: true,
|
||||
sortColumn: "fcas",
|
||||
sortOrder: "desc"
|
||||
pageSortBy: props.sortBy || props.columns[0],
|
||||
sortColumn: props.sortBy || "fcas",
|
||||
sortOrder: "desc",
|
||||
priceFilterRequired: priceFilterRequired,
|
||||
filteredColumns
|
||||
};
|
||||
}
|
||||
|
||||
@ -132,6 +178,7 @@ export default class MultiTable extends Component<Props, State> {
|
||||
page: 1,
|
||||
fontFamily: "inherit",
|
||||
columns: [
|
||||
"fcas",
|
||||
"trend",
|
||||
"userActivity",
|
||||
"developerBehavior",
|
||||
@ -144,37 +191,43 @@ export default class MultiTable extends Component<Props, State> {
|
||||
rows: {
|
||||
alternating: true,
|
||||
alternatingColors: [],
|
||||
dividers: false
|
||||
dividers: false,
|
||||
dividersColor: null,
|
||||
style: {}
|
||||
},
|
||||
trend: {
|
||||
changeOver: 7
|
||||
}
|
||||
},
|
||||
widgetType: "multi-table"
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
this._getData();
|
||||
await this._getData();
|
||||
this.updateInterval = setInterval(this._getData, 60000);
|
||||
}
|
||||
|
||||
async _getData() {
|
||||
try {
|
||||
const res = await this.props.api.fetchMetrics({
|
||||
assets: this.props.assets,
|
||||
exclusions: this.props.exclusions,
|
||||
page: this.props.page,
|
||||
size: this.props.limit,
|
||||
sort_by: COLUMNS[this.state.sortColumn].sortKey,
|
||||
sort_desc: true,
|
||||
metrics: ["fcas", "utility", "dev", "market-maturity"],
|
||||
change_over: this.props.trend.changeOver
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
rows: res.data
|
||||
});
|
||||
} catch (e) {}
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.updateInterval);
|
||||
}
|
||||
|
||||
handleSort(col: string) {
|
||||
_getData = async () => {
|
||||
const res = await this.props.api.fetchMetrics({
|
||||
assets: this.props.assets,
|
||||
exclusions: this.props.exclusions,
|
||||
page: this.props.page,
|
||||
size: this.props.limit,
|
||||
sort_by: COLUMNS[this.state.sortColumn].sortKey,
|
||||
sort_desc: true,
|
||||
metrics: mapConfigColumnsNames(this.state.filteredColumns),
|
||||
change_over: this.props.trend.changeOver
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
rows: res.data
|
||||
});
|
||||
};
|
||||
|
||||
handleClickSort(col: string) {
|
||||
if (COLUMNS[col].sortKey) {
|
||||
const sortColumn = col;
|
||||
const sortOrder = this.state.sortOrder === "asc" ? "desc" : "asc";
|
||||
@ -184,8 +237,8 @@ export default class MultiTable extends Component<Props, State> {
|
||||
|
||||
render(props: Props, state: State) {
|
||||
if (state.loading) return null;
|
||||
|
||||
const columns = ["coin", "fcas", ...props.columns];
|
||||
const coinColumn = props.showFullName ? "name" : "symbol";
|
||||
const columns = [coinColumn, ...state.filteredColumns];
|
||||
const classes = classnames("fs-multi", `fs-multi-${props.mode}`, {
|
||||
"fs-multi-alternating": props.rows.alternating,
|
||||
"fs-multi-dividers": props.rows.dividers
|
||||
@ -197,7 +250,7 @@ export default class MultiTable extends Component<Props, State> {
|
||||
sortedRows = reverse(sortedRows);
|
||||
}
|
||||
|
||||
const { fontFamily } = props;
|
||||
const { fontFamily, widgetType } = props;
|
||||
|
||||
return (
|
||||
<div class={classes} style={{ fontFamily }}>
|
||||
@ -207,7 +260,7 @@ export default class MultiTable extends Component<Props, State> {
|
||||
{props.title.text}
|
||||
</h1>
|
||||
)}
|
||||
<CustomLinks widget="multi-table" api={this.props.api} />
|
||||
<CustomLinks widget={widgetType} api={this.props.api} />
|
||||
</header>
|
||||
|
||||
<table>
|
||||
@ -219,7 +272,11 @@ export default class MultiTable extends Component<Props, State> {
|
||||
"fs-multi-sortable": !!column.sortKey
|
||||
});
|
||||
return (
|
||||
<th class={classes} onClick={() => this.handleSort(col)}>
|
||||
<th
|
||||
class={classes}
|
||||
onClick={() => this.handleClickSort(col)}
|
||||
style={props.headers.style}
|
||||
>
|
||||
<div class="fs-multi-colhead" style={props.headers.style}>
|
||||
{column.sortKey && (
|
||||
<span
|
||||
@ -244,7 +301,15 @@ export default class MultiTable extends Component<Props, State> {
|
||||
{sortedRows.map(asset => (
|
||||
<tr>
|
||||
{columns.map(col => (
|
||||
<td class={`fs-multi-${col}`}>
|
||||
<td
|
||||
class={`fs-multi-${col}`}
|
||||
style={{
|
||||
borderBottom: props.rows.dividers
|
||||
? `1px solid ${props.rows.dividersColor}`
|
||||
: null,
|
||||
...props.rows.style
|
||||
}}
|
||||
>
|
||||
{COLUMNS[col].renderItem(asset)}
|
||||
</td>
|
||||
))}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
table {
|
||||
text-align: right;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th,
|
||||
@ -18,6 +19,11 @@
|
||||
|
||||
td {
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
color: #2d57ed;
|
||||
}
|
||||
}
|
||||
|
||||
&-light {
|
||||
@ -39,9 +45,8 @@
|
||||
&-colhead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
&-text {
|
||||
flex-basis: 0;
|
||||
// flex-basis: 0;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
36
src/multiTable/types.d.ts
vendored
Normal file
36
src/multiTable/types.d.ts
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
export type Row = {
|
||||
symbol: string;
|
||||
asset_name: string;
|
||||
fcas: number;
|
||||
dev: number;
|
||||
utility: number;
|
||||
fcas_change: number;
|
||||
dev_change: number;
|
||||
utility_change: number;
|
||||
market_maturity: number;
|
||||
market_maturity_change: number;
|
||||
percent_change_7d: number;
|
||||
percent_change_24h: number;
|
||||
percent_change_1h: number;
|
||||
market_cap: number;
|
||||
price: number;
|
||||
volume_24h: number;
|
||||
};
|
||||
|
||||
// Define the columns, the content of their header, and how their data is rendered.
|
||||
type ColumnDefinition = {
|
||||
header: string;
|
||||
renderItem: (row: Row) => any;
|
||||
sortKey?: string;
|
||||
};
|
||||
|
||||
type ColumnName =
|
||||
| "fcas"
|
||||
| "trend"
|
||||
| "developerBehavior"
|
||||
| "userActivity"
|
||||
| "marketMaturity"
|
||||
| "rank"
|
||||
| "volume_24h"
|
||||
| "market_cap"
|
||||
| "price";
|
||||
@ -28,3 +28,8 @@ export function defaultsWithoutArrays(obj: object, src: object): object {
|
||||
export function defaultFlipsideLink(apiKey: string, widget: string) {
|
||||
return `https://platform-api.flipsidecrypto.com/track/${widget}/${apiKey}?redirect_url=https://flipsidecrypto.com/go-beyond-price?utm_medium=widget&utm_campaign=${widget}_widget&utm_content=letter_grade`;
|
||||
}
|
||||
|
||||
export function countDecimals(value: number) {
|
||||
if (Math.floor(value) === value) return 0;
|
||||
return value.toString().split(".")[1].length || 0;
|
||||
}
|
||||
|
||||
@ -3,13 +3,18 @@ const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
|
||||
const version = require("./package.json").version;
|
||||
const filename = `flipside-v${version}.js`;
|
||||
|
||||
const PUBLIC_PATH =
|
||||
process.env.NODE_ENV === "development"
|
||||
? "/"
|
||||
: "https://d3sek7b10w79kp.cloudfront.net/";
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/index.tsx",
|
||||
output: {
|
||||
filename: filename,
|
||||
chunkFilename: `flipside-[name]-v${version}.js`,
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
publicPath: "https://d3sek7b10w79kp.cloudfront.net/"
|
||||
publicPath: PUBLIC_PATH
|
||||
},
|
||||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user