Newer
Older
CrypticOreWallet / src / pages / dashboard / BlockDifficultyCard.tsx
@Drew Lemmy Drew Lemmy on 23 Feb 2021 4 KB feat: initial impl of work chart
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of KristWeb 2 under GPL-3.0.
// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
import React, { useState, useEffect, useMemo } from "react";
import { Card, Skeleton, Empty } from "antd";

import { useSelector } from "react-redux";
import { RootState } from "../../store";
import { useTranslation } from "react-i18next";

// import { LineCanvas } from "@nivo/line";
// import { SizeMe } from "react-sizeme";
import { Line } from "react-chartjs-2";

import { APIResponse } from "../../krist/api/types";

import { SmallResult } from "../../components/SmallResult";
import { throttle } from "lodash-es";

import Debug from "debug";
const debug = Debug("kristweb:block-difficulty-card");

export function BlockDifficultyCard(): JSX.Element {
  const { t } = useTranslation();

  const syncNode = useSelector((s: RootState) => s.node.syncNode);
  const lastBlockID = useSelector((s: RootState) => s.node.lastBlockID);
  const work = useSelector((s: RootState) => s.node.detailedWork?.work);

  const [workOverTime, setWorkOverTime] = useState<{ x: Date; y: number }[] | undefined>();
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | undefined>();

  async function _fetchWorkOverTime(): Promise<void> {
    try {
      debug("fetching work over time");

      const res = await fetch(syncNode + "/work/day");
      if (!res.ok || res.status !== 200) throw new Error(res.statusText);

      const data: APIResponse<{ work: number[] }> = await res.json();
      if (!data.ok || data.error) throw new Error(data.error);

      const processedWork = data.work.map((work, i, arr) =>
        ({ x: new Date(Date.now() - ((arr.length - i) * 60000)), y: work }));

      setWorkOverTime(processedWork);
    } catch (err) {
      console.error(err);
      setError(err);
    } finally {
      setLoading(false);
    }
  }

  const fetchWorkOverTime = useMemo(() =>
    throttle(_fetchWorkOverTime, 300, { leading: false, trailing: true }), []);

  useEffect(() => {
    if (!syncNode) return;
    fetchWorkOverTime();
  }, [syncNode, lastBlockID]);

  function chart(): JSX.Element {
    return <Line
      height={200}

      data={{
        datasets: [{
          label: "Work",

          backgroundColor: "#6495ed33",
          borderColor: "#6495ed",
          borderWidth: 2,
          pointRadius: 0,

          data: workOverTime
        }]
      }}

      options={{
        maintainAspectRatio: false,

        animation: { duration: 0 },
        hover: { animationDuration: 0 },
        responsiveAnimationDuration: 0,

        legend: {
          display: false
        },

        elements: {
          line: { tension: 0 }
        },

        tooltips: {
          mode: "index",
          intersect: false,
          displayColors: false,
          position: "nearest"
        },

        scales: {
          xAxes: [{
            type: "time",
            bounds: "data",

            ticks: {
              maxTicksLimit: 12,
              fontColor: "#8991ab",
              fontSize: 11,
              padding: 8
            },

            gridLines: {
              drawBorder: true,
              drawTicks: true,
              color: "#434a6b",
              tickMarkLength: 1,
              zeroLineWidth: 1,
              zeroLineColor: "#434a6b"
            }
          }],

          yAxes: [{
            type: "linear",

            ticks: {
              maxTicksLimit: 6,
              fontColor: "#8991ab",
              fontSize: 11,
              suggestedMin: 100,
              suggestedMax: 100000,
              beginAtZero: true,
              padding: 8
            },

            gridLines: {
              drawBorder: true,
              drawTicks: true,
              color: "#434a6b",
              tickMarkLength: 1,
              zeroLineWidth: 1,
              zeroLineColor: "#434a6b"
            }
          }]
        }
      }}
    />;
  }

  const isEmpty = !loading && error;

  return <Card title={t("dashboard.blockDifficultyCardTitle")} className={"dashboard-card dashboard-card-block-difficulty " + (isEmpty ? "empty" : "")}>
    <Skeleton paragraph={{ rows: 2 }} title={false} active loading={loading}>
      {error
        ? <SmallResult status="error" title={t("error")} subTitle={t("dashboard.blockDifficultyError")} />
        : (workOverTime
          ? chart()
          : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
        )}
    </Skeleton>
  </Card>;
}