// Copyright 2017-2020 @polkadot/app-explorer authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiProps } from '@polkadot/react-api/types';
import { Header } from '@polkadot/types/interfaces';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { CardSummary, IdentityIcon, SummaryBox } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from './translate';
interface LinkHeader {
author: string | null;
bn: string;
hash: string;
height: number;
isEmpty: boolean;
isFinalized: boolean;
parent: string;
width: number;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface LinkArray extends Array {}
interface Link {
arr: LinkArray;
hdr: LinkHeader;
}
interface Props extends ApiProps {
className?: string;
finHead?: Header;
newHead?: Header;
}
type UnsubFn = () => void;
interface Col {
author: string | null;
hash: string;
isEmpty: boolean;
isFinalized: boolean;
parent: string;
width: number;
}
interface Row {
bn: string;
cols: Col[];
}
// adjust the number of columns in a cell based on the children and tree depth
function calcWidth (children: LinkArray): number {
return Math.max(1, children.reduce((total, { hdr: { width } }): number => {
return total + width;
}, 0));
}
// counts the height of a specific node
function calcHeight (children: LinkArray): number {
return children.reduce((max, { arr, hdr }): number => {
hdr.height = hdr.isEmpty
? 0
: 1 + calcHeight(arr);
return Math.max(max, hdr.height);
}, 0);
}
// a single column in a row, it just has the details for the entry
function createCol ({ hdr: { author, hash, isEmpty, isFinalized, parent, width } }: Link): Col {
return { author, hash, isEmpty, isFinalized, parent, width };
}
// create a simplified structure that allows for easy rendering
function createRows (arr: LinkArray): Row[] {
if (!arr.length) {
return [];
}
return createRows(
arr.reduce((children: LinkArray, { arr }: Link): LinkArray =>
children.concat(...arr), [])
).concat({
bn: arr.reduce((result, { hdr: { bn } }): string =>
result || bn, ''),
cols: arr.map(createCol)
});
}
// fills in a header based on the supplied data
function createHdr (bn: string, hash: string, parent: string, author: string | null, isEmpty = false): LinkHeader {
return { author, bn, hash, height: 0, isEmpty, isFinalized: false, parent, width: 0 };
}
// empty link helper
function createLink (): Link {
return {
arr: [],
hdr: createHdr('', ' ', ' ', null, true)
};
}
// even out the columns, i.e. add empty spacers as applicable to get tree rendering right
function addColumnSpacers (arr: LinkArray): void {
// check is any of the children has a non-empty set
const hasChildren = arr.some(({ arr }): boolean => arr.length !== 0);
if (hasChildren) {
// ok, non-empty found - iterate through an add at least an empty cell to all
arr
.filter(({ arr }): boolean => arr.length === 0)
.forEach(({ arr }): number => arr.push(createLink()));
const newArr = arr.reduce((flat: LinkArray, { arr }): LinkArray => flat.concat(...arr), []);
// go one level deeper, ensure that the full tree has empty spacers
addColumnSpacers(newArr);
}
}
// checks to see if a row has a single non-empty entry, i.e. it is a candidate for collapsing
function isSingleRow (cols: Col[]): boolean {
if (!cols[0] || cols[0].isEmpty) {
return false;
}
return cols.reduce((result: boolean, col, index): boolean => {
return index === 0
? result
: (!col.isEmpty ? false : result);
}, true);
}
function renderCol ({ author, hash, isEmpty, isFinalized, parent, width }: Col, index: number): React.ReactNode {
return (
{isEmpty
?
: (
<>
{author && (
)}
{hash}
{parent}
>
)
}
);
}
// render the rows created by createRows to React nodes
function renderRows (rows: Row[]): React.ReactNode[] {
const lastIndex = rows.length - 1;
let isPrevShort = false;
return rows.map(({ bn, cols }, index): React.ReactNode => {
// if not first, not last and single only, see if we can collapse
if (index !== 0 && index !== lastIndex && isSingleRow(cols)) {
if (isPrevShort) {
// previous one was already a link, this one as well - skip it
return null;
} else if (isSingleRow(rows[index - 1].cols)) {
isPrevShort = true;
return (