diff --git a/public/locales/en.json b/public/locales/en.json index d6bdb35..47ddc2a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -671,6 +671,11 @@ "columnDifficulty": "Difficulty", "columnTime": "Time", + "mobileHeight": "Block #{{height, number}}", + "mobileMiner": "<0>Miner: <1 />", + "mobileHash": "<0>Hash: <1 />", + "mobileDifficulty": "<0>Difficulty: <1 />", + "tableTotal": "{{count, number}} block", "tableTotal_plural": "{{count, number}} blocks", "tableTotalEmpty": "No blocks" @@ -1160,7 +1165,13 @@ "namesARecord": "A Record", "namesUnpaid": "Unpaid Blocks", "namesRegistered": "Registered Time", - "namesUpdated": "Updated Time" + "namesUpdated": "Updated Time", + + "blocksMiner": "Miner", + "blocksHash": "Hash", + "blocksValue": "Value", + "blocksDifficulty": "Difficulty", + "blocksTime": "Time" } } } diff --git a/src/pages/blocks/BlockMobileItem.tsx b/src/pages/blocks/BlockMobileItem.tsx new file mode 100644 index 0000000..e84049f --- /dev/null +++ b/src/pages/blocks/BlockMobileItem.tsx @@ -0,0 +1,80 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under AGPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useCallback } from "react"; + +import { Trans } from "react-i18next"; +import { useTFns } from "@utils/i18n"; + +import { KristBlock } from "@api/types"; + +import { KristValue } from "@comp/krist/KristValue"; +import { ContextualAddress } from "@comp/addresses/ContextualAddress"; +import { DateTime } from "@comp/DateTime"; + +interface Props { + block: KristBlock; +} + +export function BlockMobileItem({ block }: Props): JSX.Element { + const { t, tKey } = useTFns("blocks."); + + const Hash = useCallback(() => ( + + {block.hash?.substr(0, 12) || ""} + + ), [block.hash]); + + const Difficulty = useCallback(() => ( + + {block.difficulty.toLocaleString()} + + ), [block.difficulty]); + + return
+ {/* Block value */} +
+ +
+ + {/* Block height */} +
+ {t(tKey("mobileHeight"), { height: block.height })} +
+ + {/* Miner */} +
+ + Miner: + + +
+ +
+ {/* Hash */} + {block.hash && <> + + + Hash: + + + + + } + + {/* Difficulty */} + + + Difficulty: + + + +
+ + + {/* Mined time */} +
+ +
+
; +} diff --git a/src/pages/blocks/BlocksPage.less b/src/pages/blocks/BlocksPage.less new file mode 100644 index 0000000..21ad869 --- /dev/null +++ b/src/pages/blocks/BlocksPage.less @@ -0,0 +1,51 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under AGPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +@import (reference) "../../App.less"; +@import "../../style/table.less"; + +.blocks-page .table-mobile-list-view { + .block-mobile-item { + .block-height { + display: block; + font-size: 120%; + } + + .block-value { + float: right; + font-size: 120%; + } + + .block-field { + font-weight: bold; + white-space: nowrap; + color: @text-color-secondary; + } + + .block-technical-row { + display: block; + + .block-mobile-hash, .block-difficulty { + font-size: 90%; + } + + .block-mobile-hash-value { + color: @text-color; + font-family: monospace; + } + + .sep:before { + content: "\2013"; + + display: inline-block; + margin: 0 @padding-xs; + color: @text-color-secondary; + } + } + + .block-mined { + color: @text-color-secondary; + font-size: @font-size-sm; + } + } +} diff --git a/src/pages/blocks/BlocksPage.tsx b/src/pages/blocks/BlocksPage.tsx index 0a860ee..cceaa27 100644 --- a/src/pages/blocks/BlocksPage.tsx +++ b/src/pages/blocks/BlocksPage.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { useState, useMemo } from "react"; +import { useState, useMemo, useEffect } from "react"; import { useSelector } from "react-redux"; import { RootState } from "@store"; @@ -12,6 +12,9 @@ import { useBooleanSetting } from "@utils/settings"; import { useLinkedPagination } from "@utils/table/table"; +import { useTopMenuOptions } from "@layout/nav/TopMenu"; + +import "./BlocksPage.less"; interface Props { lowest?: boolean; @@ -30,6 +33,9 @@ // If auto-refresh is disabled, use a static refresh ID const usedRefreshID = shouldAutoRefresh ? lastBlockID : 0; + const [,, unset, setOpenSortModal] = useTopMenuOptions(); + useEffect(() => unset, [unset]); + // Memoise the table so that it only updates the props (thus triggering a // re-fetch of the blocks) when something relevant changes const memoTable = useMemo(() => ( @@ -38,8 +44,9 @@ lowest={lowest} setError={setError} setPagination={setPagination} + setOpenSortModal={setOpenSortModal} /> - ), [usedRefreshID, lowest, setError, setPagination]); + ), [usedRefreshID, lowest, setError, setPagination, setOpenSortModal]); return >; - setPagination?: Dispatch>; -} - function getColumns( tStr: TStrFn, dateColumnWidth: number, @@ -111,28 +107,48 @@ ]; } -export function BlocksTable({ refreshingID, lowest, setError, setPagination }: Props): JSX.Element { - const { tStr, tKey } = useTFns("blocks."); +const sortOptions: SortOptions = [ + { sortKey: "address", i18nKey: "blocksMiner" }, + { sortKey: "hash", i18nKey: "blocksHash" }, + { sortKey: "value", i18nKey: "blocksValue" }, + { sortKey: "difficulty", i18nKey: "blocksDifficulty" }, + { sortKey: "time", i18nKey: "blocksTime" } +]; + +interface Props { + // Number used to trigger a refresh of the blocks listing + refreshingID?: number; + lowest?: boolean; + + setError?: Dispatch>; + setPagination?: Dispatch>; + setOpenSortModal?: SetOpenSortModalFn; +} + +export function BlocksTable({ + refreshingID, + lowest, + setError, + setPagination, + setOpenSortModal +}: Props): JSX.Element { + const { tKey } = useTFns("blocks."); + + const defaultOrderBy = lowest ? "hash" : "time"; + const defaultOrder = lowest ? "ASC" : "DESC"; const [loading, setLoading] = useState(true); const [res, setRes] = useState(); const { options, setOptions } = useTableHistory({ - orderBy: lowest ? "hash" : "height", - order: lowest ? "ASC" : "DESC" + orderBy: defaultOrderBy, order: defaultOrder }); - const { paginationTableProps, hotkeys } = useMalleablePagination( + const { paginationTableProps, paginationChange, hotkeys } = useMalleablePagination( res, res?.blocks, tKey("tableTotal"), options, setOptions, setPagination ); - const dateColumnWidth = useDateColumnWidth(); - - const columns = useMemo(() => getColumns( - tStr, dateColumnWidth, lowest - ), [tStr, dateColumnWidth, lowest]); - // Fetch the blocks from the API, mapping the table options useEffect(() => { debug("looking up blocks"); @@ -146,7 +162,56 @@ debug("results? %b res.blocks.length: %d res.count: %d res.total: %d", !!res, res?.blocks?.length, res?.count, res?.total); - const tbl = + const renderMobileItem: RenderItem = useCallback(block => ( + + ), []); + + const { isMobile, list } = useMobileList( + loading, res?.blocks || [], "height", + paginationTableProps.pagination, paginationChange, + sortOptions, defaultOrderBy, defaultOrder, + options, setOptions, setOpenSortModal, + renderMobileItem + ); + + return <> + {isMobile && list + ? list + : } + {hotkeys} + ; +} + +interface DesktopViewProps { + loading: boolean; + res?: LookupBlocksResponse; + + lowest?: boolean; + + paginationTableProps: PaginationTableProps; +} + +function DesktopView({ + loading, res, + lowest, + paginationTableProps, +}: DesktopViewProps): JSX.Element { + const { tStr } = useTFns("blocks."); + + const dateColumnWidth = useDateColumnWidth(); + + const columns = useMemo(() => getColumns( + tStr, dateColumnWidth, lowest + ), [tStr, dateColumnWidth, lowest]); + + return className="blocks-table" size="small" scroll={{ x: true }} @@ -159,9 +224,4 @@ columns={columns} />; - - return <> - {tbl} - {hotkeys} - ; } diff --git a/src/pages/names/NamesTable.tsx b/src/pages/names/NamesTable.tsx index a8f6149..70d1b2a 100644 --- a/src/pages/names/NamesTable.tsx +++ b/src/pages/names/NamesTable.tsx @@ -263,7 +263,7 @@ loading: boolean; res?: LookupNamesResponse; - sortNew: boolean | undefined; + sortNew?: boolean; paginationTableProps: PaginationTableProps;