Merge pull request #1 from FlipsideCrypto/spectrum-updated

Spectrum updates
This commit is contained in:
Jim Myers 2019-02-21 09:56:52 -05:00 committed by GitHub
commit 88d736fda3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 418 additions and 614 deletions

View File

@ -23,6 +23,7 @@
"@types/lodash": "^4.14.120",
"@types/node": "^10.12.19",
"babel-loader": "^8.0.4",
"core-js": "^2.6.5",
"css-loader": "^1.0.1",
"css-modules-typescript-loader": "^1.1.1",
"node-sass": "^4.10.0",

View File

@ -79,7 +79,7 @@ export default class API {
export type WidgetLinksSlug = "spectrum" | "multi-table" | "table" | "score";
export type WidgetLinksLink = {
widget_id: string;
name: "right_link" | "left_link";
name: string;
link_html: string;
};
export type WidgetLinksResponse = {

View File

@ -44,8 +44,15 @@ class CustomLinks extends Component<Props, State> {
const leftLink = find(state.links, { name: "left_link" });
const rightLink = find(state.links, { name: "right_link" });
const topLink = find(state.links, { name: "top_link" });
return (
<div class={css.wrapper} style={props.style}>
{topLink && (
<span
class={linkClass}
dangerouslySetInnerHTML={{ __html: topLink.link_html }}
/>
)}
{leftLink && (
<span
class={linkClass}

View File

@ -4,7 +4,8 @@ import * as css from "./style.css";
type Props = {
score: number;
kind?: "slim" | "large";
kind?: "slim" | "normal" | "large";
class?: string;
};
type State = {
@ -42,10 +43,7 @@ export default class Rank extends Component<Props, State> {
rankClass = css.s;
}
let kindClass = css.slim;
if (props.kind === "large") {
kindClass = css.large;
}
let kindClass = css[props.kind];
const classes = classNames(css.rank, rankClass, kindClass);
return (

View File

@ -39,6 +39,12 @@
width: 32px;
}
.normal {
background-size: auto 70%;
width: 54px;
height: 22px;
}
.large {
background-size: auto 48%;
height: 28px;

View File

@ -5,6 +5,7 @@ export const b: string;
export const c: string;
export const f: string;
export const large: string;
export const normal: string;
export const rank: string;
export const s: string;
export const slim: string;

View File

@ -23,7 +23,7 @@ const Trend = (props: Props) => {
}
const difference = calculateTrendDiff(props.value, props.change);
const classes = classNames(directionClass, props.class);
const classes = classNames(css.wrapper, directionClass, props.class);
return (
<span class={classes}>

View File

@ -1,7 +1,11 @@
.wrapper {
display: flex;
align-items: center;
}
.icon {
height: 6px;
margin-right: 2px;
vertical-align: middle;
}
.up {

View File

@ -3,3 +3,4 @@
export const down: string;
export const icon: string;
export const up: string;
export const wrapper: string;

View File

@ -1,8 +1,12 @@
// ie11 polyfills
import "core-js/fn/promise";
import "core-js/fn/object/assign";
import { h, render } from "preact";
import API from "./api";
import Table from "./table";
import { defaultsWithoutArrays } from "./utils";
import SpectrumPlot, { Props as SpectrumPlotProps } from "./spectrumPlot";
import Spectrum, { Props as SpectrumProps } from "./spectrum";
import MultiTable, { Props as MultiTableProps } from "./multiTable";
import Score, { Props as ScoreProps } from "./score";
@ -19,10 +23,10 @@ export default class Flipside {
render(<MultiTable {...props} api={this.api} />, element);
}
spectrum(el: string, opts: SpectrumPlotProps): void {
spectrum(el: string, opts: SpectrumProps): void {
const element = document.getElementById(el);
const props = defaultsWithoutArrays(SpectrumPlot.defaultProps, opts);
render(<SpectrumPlot {...props} api={this.api} />, element);
const props = defaultsWithoutArrays(Spectrum.defaultProps, opts);
render(<Spectrum {...props} api={this.api} />, element);
}
score(el: string, opts: ScoreProps) {

View File

@ -34,6 +34,8 @@
.change {
font-size: 15px;
font-weight: 600;
display: flex;
justify-content: flex-end;
}
.trend {

View File

@ -0,0 +1,51 @@
import { h, Component, ComponentChildren } from "preact";
import classNames from "classnames";
import * as css from "./style.css";
type Props = {
mode: "light" | "dark";
items: any[];
renderSlide: any;
};
type State = {
currentSlide: number;
};
export default class Carousel extends Component<Props, State> {
state = {
currentSlide: 0
};
slideTo = (slide: number) => {
this.setState({ currentSlide: slide });
};
render(props: Props, state: State) {
const carouselOffset = state.currentSlide * 100;
const carouselStyle = {
transform: `translateX(-${carouselOffset}%)`
};
return (
<div class={classNames(css.wrapper, css[props.mode])}>
<div class={css.carousel} style={carouselStyle}>
{props.items.map(item => (
<div class={css.slide}>{props.renderSlide(item)}</div>
))}
</div>
{props.items.length > 1 && (
<div class={css.dots}>
{props.items.map((_, i) => {
const classes = classNames(css.dotItem, {
[css.dotActive]: state.currentSlide === i
});
return <div class={classes} onClick={() => this.slideTo(i)} />;
})}
</div>
)}
</div>
);
}
}

View File

@ -0,0 +1,46 @@
.dark .dotItem {
background: rgba(255, 255, 255, 0.25);
}
.dark .dotActive {
background: rgba(255, 255, 255, 1);
}
.light .dotItem {
background: rgba(0, 0, 0, 0.15);
}
.light .dotActive {
background: rgba(0, 0, 0, 1);
}
.wrapper {
width: 100%;
overflow: hidden;
}
.carousel {
transition: transform 250ms ease-out;
white-space: nowrap;
}
.slide {
width: 100%;
display: inline-block;
}
.dots {
display: flex;
justify-content: center;
margin-top: 10px;
}
.dotItem {
border-radius: 5px;
height: 10px;
width: 10px;
margin: 0 5px;
transition: background 250ms ease-out;
cursor: pointer;
}
.dotActive {
}

View File

@ -0,0 +1,10 @@
// This file is automatically generated.
// Please do not change this file!
export const carousel: string;
export const dark: string;
export const dotActive: string;
export const dotItem: string;
export const dots: string;
export const light: string;
export const slide: string;
export const wrapper: string;

View File

@ -1,12 +1,13 @@
import { h, Component } from "preact";
import classNames from "classnames";
import CustomLinks from "../components/customLinks";
import Score from "./score";
import Plot from "./plot";
import "./styles.scss";
import * as css from "./style.css";
import API, { WidgetLinksLink } from "../api";
import Rank from "../components/rank";
import Trend from "../components/trend";
import Carousel from "./components/carousel";
type BootstrapAssetType = {
export type BootstrapAssetType = {
value: number;
percent_change: number;
asset_name: string;
@ -17,7 +18,7 @@ type BootstrapHighlightType = {
value: number;
};
type AssetType = {
export type AssetType = {
symbol: string;
highlights?: string[];
bootstrapAsset?: BootstrapAssetType;
@ -49,12 +50,20 @@ export type Props = {
};
type State = {
metric: any;
loading: boolean;
metric: {
name: string;
fcas: number;
change: number;
};
};
class SpectrumPlot extends Component<Props, State> {
interval: NodeJS.Timeout;
class Spectrum extends Component<Props, State> {
interval: number;
static defaultProps = {
mode: "light"
};
constructor() {
super();
@ -96,7 +105,7 @@ class SpectrumPlot extends Component<Props, State> {
}
_update() {
this.interval = setInterval(async () => {
this.interval = window.setInterval(async () => {
await this._getData();
}, 300000);
}
@ -111,86 +120,44 @@ class SpectrumPlot extends Component<Props, State> {
this.setState({
loading: false,
metric: {
fcas: "NA",
change: "NA",
name: "NA"
name: "NA",
fcas: 0,
change: 0
}
});
}
this._update();
}
render(props: Props, { metric, loading }: State) {
if (loading) return null;
return (
<div>
<Score symbol={props.asset.symbol} metric={metric} {...props} mini />
{props.spectrum.enabled && <Plot metric={metric} {...props} />}
</div>
);
}
}
render(props: Props, state: State) {
if (state.loading) return null;
type CarouselState = {
currentSlide: number;
};
export default class Carousel extends Component<Props, CarouselState> {
state = {
currentSlide: 0
};
static defaultProps: Props = {
asset: {
symbol: "btc",
highlights: ["eth", "zec", "zrx"],
bootstrapAsset: null,
bootstrapHighlights: null
},
disableLinks: false,
assets: [],
mode: "light",
fontFamily: "inherit",
relatedMarkers: {
enabled: true,
bucketDistance: 35,
lineDistance: 25,
fontFamily: "inherit"
},
name: { enabled: true },
spectrum: { enabled: true },
icon: { enabled: true },
rank: { enabled: true },
trend: { enabled: true },
linkBootstrap: null
};
slideTo = (slide: number) => {
this.setState({ currentSlide: slide });
};
render(props: Props, state: CarouselState) {
let assets = [props.asset];
if (props.assets.length > 0) {
assets = props.assets;
}
const carouselOffset = state.currentSlide * 100;
const carouselStyle = {
transform: `translateX(-${carouselOffset}%)`
};
const { asset, mode } = props;
const { metric } = state;
return (
<div class={`fs-spectrum fs-spectrum-${props.mode}`}>
<div class="fs-spectrum-viewport">
<div class="fs-spectrum-carousel" style={carouselStyle}>
{assets.map(asset => (
<div class="fs-spectrum-carousel-item">
<SpectrumPlot {...props} asset={asset} />
</div>
))}
</div>
<div class={css[mode]}>
<div class={css.header}>
<img
class={css.icon}
src={`https://d301yvow08hyfu.cloudfront.net/svg/color/${asset.symbol.toLowerCase()}.svg`}
/>
<span class={css.name}>{metric.name}</span>
</div>
<div class={css.meta}>
<span class={css.symbol}>{asset.symbol}</span>
<span class={css.fcas}>FCAS {metric.fcas}</span>
<span class={css.trend}>
<Trend change={metric.change} value={metric.fcas} />
</span>
<span class={css.rank}>
<Rank score={metric.fcas} kind="normal" />
</span>
</div>
{props.spectrum.enabled && <Plot metric={metric} {...props} />}
{props.disableLinks === false && (
<CustomLinks
widget="spectrum"
@ -198,18 +165,49 @@ export default class Carousel extends Component<Props, CarouselState> {
linkBootstrap={props.linkBootstrap}
/>
)}
{assets.length > 1 && (
<div class="fs-spectrum-dots">
{assets.map((_, i) => {
const classes = classNames("fs-spectrum-dot", {
"fs-spectrum-dot-active": state.currentSlide === i
});
return <div class={classes} onClick={() => this.slideTo(i)} />;
})}
</div>
)}
</div>
);
}
}
const MultiSpectrum = (props: Props) => {
let assets = [props.asset];
if (props.assets.length > 0) {
assets = props.assets;
}
return (
<Carousel
mode={props.mode}
items={assets}
renderSlide={(item: any) => <Spectrum {...props} asset={item} />}
/>
);
};
MultiSpectrum.defaultProps = {
asset: {
symbol: "btc",
highlights: ["eth", "zec", "zrx"],
bootstrapAsset: null,
bootstrapHighlights: null
},
disableLinks: false,
assets: [],
mode: "light",
fontFamily: "inherit",
relatedMarkers: {
enabled: true,
bucketDistance: 35,
lineDistance: 25,
fontFamily: "inherit"
},
name: { enabled: true },
spectrum: { enabled: true },
icon: { enabled: true },
rank: { enabled: true },
trend: { enabled: true },
linkBootstrap: null
} as Props;
export default MultiSpectrum;

View File

@ -1,13 +1,20 @@
import { h, Component } from "preact";
import { sortObjectArray } from "../../utils";
import "./styles.scss";
import * as css from "./style.css";
const PLOT_WIDTH = 240;
const PLOT_SCALE = PLOT_WIDTH / 1000;
const DEFAULT_BUCKET_DISTANCE = 35;
const DEFAULT_LINE_DISTANCE = 25;
export default class Plot extends Component {
// TODO: Port this component to TS
// type Props = {
// mode: "light" | "dark"
// }
export default class Plot extends Component<any, any> {
interval: any;
constructor() {
super();
this.state = {
@ -48,13 +55,13 @@ export default class Plot extends Component {
}
async _getHighlights() {
const highlights = this.getHighlights();
const highlights: any[] = this.getHighlights();
let nextHighlightState = [];
let nextHighlightedSymbolState = [];
if (this.props.asset && this.props.asset.bootstrapHighlights) {
nextHighlightState = this.props.asset.bootstrapHighlights;
nextHighlightedSymbolState = this.props.asset.bootstrapHighlights.map(
highlight => highlight.symbol
(highlight: any) => highlight.symbol
);
} else {
await Promise.all(
@ -118,7 +125,7 @@ export default class Plot extends Component {
return highlights;
}
getBuckets() {
getBuckets(): any {
if (this.state.highlights.length == 0) {
return [];
}
@ -128,10 +135,10 @@ export default class Plot extends Component {
bucketDistance = DEFAULT_BUCKET_DISTANCE;
}
let buckets = [];
let buckets: any[] = [];
let currentBucketIndex = 0;
let anchorX = 0;
let scoresToBuckets = {};
let scoresToBuckets: any = {};
let highlightLength = this.state.highlights.length;
let sortedHighLights = sortObjectArray(this.state.highlights, "value");
@ -164,7 +171,7 @@ export default class Plot extends Component {
return { buckets, scoresToBuckets };
}
getYCoords(asset, buckets, scoresToBuckets) {
getYCoords(asset: any, buckets: any, scoresToBuckets: any) {
let { lineDistance } = this.props.relatedMarkers;
if (!lineDistance) {
lineDistance = DEFAULT_LINE_DISTANCE;
@ -192,7 +199,7 @@ export default class Plot extends Component {
return { y: 44 - 10 * index, toClose };
}
render(props, { loading, distribution }) {
render(props: any, { loading, distribution }: any) {
if (loading) return null;
const highlightedSymbols = this.state.highlightedSymbols;
@ -202,8 +209,8 @@ export default class Plot extends Component {
const xPos = `${(props.metric.fcas / 1000) * 100}%`;
const highlightedAssets = distribution
.filter(i => highlightedSymbols.indexOf(i.symbol) > -1)
.filter(i => i.symbol != props.asset.symbol.toUpperCase());
.filter((i: any) => highlightedSymbols.indexOf(i.symbol) > -1)
.filter((i: any) => i.symbol != props.asset.symbol.toUpperCase());
const { buckets, scoresToBuckets } = this.getBuckets();
@ -220,7 +227,8 @@ export default class Plot extends Component {
}
return (
<svg class="fs-plot" width="100%" height="104" overflow="visible">
// @ts-ignore
<svg width="100%" height="104" overflow="visible" class={css[props.mode]}>
<defs>
<linearGradient id="gradient">
<stop stop-color="#ff2600" offset="0%" />
@ -245,7 +253,7 @@ export default class Plot extends Component {
: "rgba(0, 0, 0, 0.4)"
}
>
{distribution.map(i => (
{distribution.map((i: any) => (
<circle cx={`${(i.value / 1000) * 100}%`} cy="58" r="2.5" />
))}
</g>
@ -256,7 +264,7 @@ export default class Plot extends Component {
{/* Spectrum Legend */}
<text
y="85"
class="fs-plot-legend"
class={css.legend}
fill={props.mode === "dark" ? "#fff" : "#000"}
>
<tspan text-anchor="start" x="0">
@ -271,11 +279,11 @@ export default class Plot extends Component {
</text>
{props.relatedMarkers.enabled &&
highlightedAssets.map(a => {
highlightedAssets.map((a: any) => {
const xPos = `${(a.value / 1000) * 100}%`;
let { y, toClose } = this.getYCoords(a, buckets, scoresToBuckets);
return (
<g class="fs-plot-related" style={relatedLabelStyle}>
<g class={css.related} style={relatedLabelStyle}>
<text x={xPos} y={y} text-anchor="middle" font-size="10">
{a.symbol}
</text>
@ -285,7 +293,7 @@ export default class Plot extends Component {
y1={y + 3}
x2={xPos}
y2="60"
class="fs-plot-related-line"
class={css.relatedLine}
style={relatedLineStyle}
/>
)}
@ -294,18 +302,14 @@ export default class Plot extends Component {
})}
{/* Blue FCAS Marker */}
<text
class="fs-plot-asset-marker"
text-anchor="middle"
font-weight="bold"
>
<text class={css.marker} text-anchor="middle" font-weight="bold">
<tspan x={xPos} y={26}>
{props.asset.symbol.toUpperCase()}
</tspan>
</text>
{/* Blue FCAS Marker Line */}
<line x1={xPos} y1={28} x2={xPos} y2={60} class="fs-plot-asset-line" />
<line x1={xPos} y1={28} x2={xPos} y2={60} class={css.line} />
</svg>
);
}

View File

@ -0,0 +1,41 @@
.light .marker {
fill: #2d57ed;
}
.light .line {
stroke: #2d57ed;
}
.light .related {
fill: #000;
}
.light .relatedLine {
stroke: #000;
}
.dark .marker {
fill: #20b7fc;
}
.dark .line {
stroke: #20b7fc;
}
.dark .related {
fill: #fff;
}
.dark .relatedLine {
stroke: #fff;
}
.marker {
font-size: 12px;
}
.line {
stroke-width: 1;
}
.relatedLine {
stroke-width: 0.5;
}
.legend {
font-size: 7px;
}

9
src/spectrum/plot/style.css.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// This file is automatically generated.
// Please do not change this file!
export const dark: string;
export const legend: string;
export const light: string;
export const line: string;
export const marker: string;
export const related: string;
export const relatedLine: string;

60
src/spectrum/style.css Normal file
View File

@ -0,0 +1,60 @@
.light .meta,
.light a {
color: #2d57ed;
}
.light .name {
color: #000;
}
.dark .meta,
.dark a {
color: #20b7fc;
}
.dark .name {
color: #fff;
}
.header {
display: flex;
align-items: center;
margin-bottom: 24px;
}
.icon {
height: 28px;
width: 28px;
margin-right: 14px;
}
.name {
font-size: 24px;
font-weight: normal;
}
.meta {
display: flex;
align-items: center;
text-transform: uppercase;
margin-bottom: 16px;
font-size: 16px;
}
.symbol {
margin-right: auto;
}
.fcas {
margin: 0 6px;
}
.rank {
margin-left: 9px;
}
.trend {
font-size: 10px;
margin-left: 8px;
font-weight: 600;
}

12
src/spectrum/style.css.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
// This file is automatically generated.
// Please do not change this file!
export const dark: string;
export const fcas: string;
export const header: string;
export const icon: string;
export const light: string;
export const meta: string;
export const name: string;
export const rank: string;
export const symbol: string;
export const trend: string;

View File

@ -1,15 +0,0 @@
.fs-plot-asset-marker {
font-size: 12px;
}
.fs-plot-asset-line {
stroke-width: 1;
}
.fs-plot-related-line {
stroke-width: 0.5;
}
.fs-plot-legend {
font-size: 7px;
}

View File

@ -1,105 +0,0 @@
import { h, Component } from "preact";
function round(value) {
if (!value) {
return value;
}
return Math.round(value * 100) / 100;
}
export default class Data 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, metric, rank }, { showTooltip }) {
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>
<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-score-trend">
<div
class={`fs-score-trend__change fs-score-trend__change--${trendDir}`}
>
<img class="fs-score-trend__icon" src={trendIcon} />{" "}
{round(trendDiff)}
</div>
7d
</div>
)}
{opts.rank && (
<div class="fs-score-rank">
<b class={`fs-score-rank__letter fs-score-rank__letter--${rank}`}>
{rank}
</b>{" "}
Rank
</div>
)}
</div>
</div>
);
}
}

View File

@ -1,16 +0,0 @@
import { h } from "preact";
export default props => {
return (
<div class="fs-data-mini">
FCAS {props.metric.fcas}
<div
class={`fs-score-rank fs-score-rank__letter fs-score-rank__letter--mini fs-score-rank__letter--${
props.rank
}`}
>
{props.rank}
</div>
</div>
);
};

View File

@ -1,9 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 630 B

View File

@ -1,9 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 534 B

View File

@ -1,9 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 531 B

View File

@ -1,57 +0,0 @@
import { h, Component } from "preact";
import Data from "./Data";
import DataMini from "./DataMini";
import "./styles.scss";
function round(value) {
if (!value) {
return value;
}
return Math.round(value * 100) / 100;
}
export default class Score extends Component {
render(props, { showTooltip }) {
let rank;
if (props.metric.fcas <= 500) {
rank = "f";
} else if (props.metric.fcas <= 649) {
rank = "c";
} else if (props.metric.fcas <= 749) {
rank = "b";
} else if (props.metric.fcas <= 899) {
rank = "a";
} else {
rank = "s";
}
let wrapperClass = "fs-score";
if (props.mode === "dark") wrapperClass += " fs-score--dark";
if (props.mini) wrapperClass += " fs-score--mini";
const DataComponent = props.mini ? DataMini : Data;
return (
<div class={wrapperClass}>
<header class="fs-token">
{props.icon.enabled && (
<img
class="fs-token__logo"
src={`https://d301yvow08hyfu.cloudfront.net/svg/color/${props.asset.symbol.toLowerCase()}.svg`}
/>
)}
<h1 class="fs-token__name">
<span style={props.name.style}>
{props.name.enabled && props.metric.name}
</span>
{props.asset && (
<span class="fs-token__sym">{props.asset.symbol}</span>
)}
</h1>
</header>
{props.rank.enabled && <DataComponent {...props} rank={rank} />}
</div>
);
}
}

View File

@ -1,170 +0,0 @@
.fs-score {
margin-bottom: 25px;
}
.fs-score--dark {
color: #fff;
}
.fs-score--mini {
margin-bottom: 10px;
}
.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-data-mini {
font-size: 14px;
color: #2d57ed;
display: flex;
align-items: center;
}
.fs-value {
font-size: 58px;
font-weight: bold;
margin: 0 14px 0 0;
}
.fs-score-trend {
margin-right: 16px;
}
.fs-score-trend__icon {
margin-right: 2px;
}
.fs-score-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-score-rank {
text-align: center;
font-size: 10px;
}
.fs-score-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;
color: #000;
}
.fs-score-rank__letter--mini {
height: 22px;
line-height: 22px;
width: 50px;
margin: 0 0 0 auto;
}
.fs-score-rank__letter--s {
background: #68ba66;
}
.fs-score-rank__letter--a {
background: #8fcb89;
}
.fs-score-rank__letter--b {
background: #b2dbad;
}
.fs-score-rank__letter--c {
background: #ff7a18;
}
.fs-score-rank__letter--f {
background: #ff2600;
}

View File

@ -1,90 +0,0 @@
.fs-spectrum {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
line-height: 1;
border-radius: 25px;
&-viewport {
width: 100%;
overflow: hidden;
}
&-carousel {
transition: transform 250ms ease-out;
white-space: nowrap;
}
&-carousel-item {
width: 100%;
display: inline-block;
}
&-dots {
display: flex;
justify-content: center;
margin-top: 10px;
}
&-dot {
border-radius: 5px;
height: 10px;
width: 10px;
margin: 0 5px;
transition: background 250ms ease-out;
cursor: pointer;
}
}
.fs-spectrum-light {
color: #000;
a {
color: #2d57ed;
}
.fs-spectrum-dot {
background: rgba(0, 0, 0, 0.15);
&-active {
background: rgba(0, 0, 0, 1);
}
}
.fs-plot-asset-marker {
fill: #2d57ed;
}
.fs-plot-asset-line {
stroke: #2d57ed;
}
.fs-plot-related {
fill: #000;
}
.fs-plot-related-line {
stroke: #000;
}
}
.fs-spectrum-dark {
color: #fff;
a {
color: #20b7fc;
}
.fs-spectrum-dot {
background: rgba(255, 255, 255, 0.25);
&-active {
background: rgba(255, 255, 255, 1);
}
}
.fs-plot-asset-marker {
fill: #20b7fc;
}
.fs-plot-asset-line {
stroke: #20b7fc;
}
.fs-plot-related {
fill: #fff;
}
.fs-plot-related-line {
stroke: #fff;
}
}
.fs-link {
color: #2d57ed;
text-decoration: underline;
cursor: pointer;
}

View File

@ -1,9 +1,10 @@
import { h, Component } from "preact";
import keyBy from "lodash/keyBy";
import keyBy = require("lodash/keyBy");
import CustomLinks from "./components/customLinks";
import "./styles.scss";
import API from "../api";
function getMetricTrend(change) {
function getMetricTrend(change: number) {
if (change < 0) {
return "down";
} else if (change == 0) {
@ -13,11 +14,23 @@ function getMetricTrend(change) {
}
}
function calculateDiff(value, percent) {
function calculateDiff(value: number, percent: number) {
return Math.round(Math.abs(value * (percent / 100)));
}
export default class Table extends Component {
type Props = {
api: API;
symbol: string;
dark: boolean;
borderColor?: string;
};
type State = {
loading: boolean;
metrics: any;
};
export default class Table extends Component<Props, State> {
constructor() {
super();
this.state = { loading: true, metrics: null };
@ -50,7 +63,7 @@ export default class Table extends Component {
window.location.assign(learnMoreUrl);
}
render({ dark }, { loading, metrics }) {
render({ dark }: Props, { loading, metrics }: State) {
if (loading) {
return null;
}
@ -94,7 +107,7 @@ export default class Table extends Component {
</tr>
<tr>
<th style={tdStyle} colspan="2">
<th style={tdStyle} colSpan={2}>
User Activity
</th>
<td style={tdStyle}>{utility.value}</td>
@ -107,7 +120,7 @@ export default class Table extends Component {
</tr>
<tr>
<th style={tdStyle} colspan="2">
<th style={tdStyle} colSpan={2}>
Developer Behavior
</th>
<td style={tdStyle}>{dev.value}</td>

View File

@ -4,9 +4,10 @@
"noImplicitAny": true,
"sourceMap": true,
"module": "commonjs",
"target": "es6",
"target": "es5",
"lib": ["es2015", "dom"],
"jsx": "react",
"jsxFactory": "h",
"allowJs": true
"allowJs": true,
"jsxFactory": "h"
}
}

View File

@ -102,6 +102,13 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-jsx" "^7.0.0"
"@babel/polyfill@^7.2.5":
version "7.2.5"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
dependencies:
core-js "^2.5.7"
regenerator-runtime "^0.12.0"
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
@ -976,6 +983,10 @@ copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
core-js@^2.5.7, core-js@^2.6.5:
version "2.6.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -3339,6 +3350,10 @@ regenerate@^1.2.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
regenerator-runtime@^0.12.0:
version "0.12.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"