From f87b1f7b7280121a8a93105575e3ff7faf3fbf67 Mon Sep 17 00:00:00 2001 From: Albert G <516972+alber70g@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:48:36 +0100 Subject: [PATCH] feat(explorer): reorder columns. RequestKey is the relevant first column when showing transactions. Moved sender to the end (#2754) * feat(explorer): reorder columns. RequestKey is the relevant first column when showing transactions. Moved sender to the end * feat(explorer): use proper layout for no results found * feat(explorer): do not close truncated parts --- .changeset/soft-tomatoes-know.md | 8 ++ .../BlockTransactions/BlockTransactions.tsx | 16 ++-- .../ExpandTruncatedField.tsx | 49 +----------- .../NoSearchResults/NoSearchResults.tsx | 28 +++---- .../pages/[networkSlug]/event/[eventname].tsx | 4 +- .../transaction/[requestKey].tsx | 9 ++- .../src/utils/__test__/condenseString.test.ts | 79 +++++++++++++++++++ .../explorer/src/utils/condenseStrings.ts | 43 ++++++++++ 8 files changed, 159 insertions(+), 77 deletions(-) create mode 100644 .changeset/soft-tomatoes-know.md create mode 100644 packages/apps/explorer/src/utils/__test__/condenseString.test.ts create mode 100644 packages/apps/explorer/src/utils/condenseStrings.ts diff --git a/.changeset/soft-tomatoes-know.md b/.changeset/soft-tomatoes-know.md new file mode 100644 index 0000000000..1b48f759da --- /dev/null +++ b/.changeset/soft-tomatoes-know.md @@ -0,0 +1,8 @@ +--- +'@kadena/explorer': patch +--- + +Reordering columns to present the first column as the one to link to + +Remove toggling close of truncated content. Will always open so users can no +select and copy diff --git a/packages/apps/explorer/src/components/BlockTransactions/BlockTransactions.tsx b/packages/apps/explorer/src/components/BlockTransactions/BlockTransactions.tsx index 92544864ea..7560f2edc0 100644 --- a/packages/apps/explorer/src/components/BlockTransactions/BlockTransactions.tsx +++ b/packages/apps/explorer/src/components/BlockTransactions/BlockTransactions.tsx @@ -94,14 +94,7 @@ export const BlockTransactions: FC = ({ hash }) => { loaderVariant: 'icon', }, { - label: 'Sender', - key: 'cmd.meta.sender', - variant: 'code', - width: '25%', - render: FormatLinkWrapper({ url: '/account/:value' }), - }, - { - label: 'RequestKey', + label: 'Request Key', key: 'hash', variant: 'code', width: '25%', @@ -114,6 +107,13 @@ export const BlockTransactions: FC = ({ hash }) => { width: '40%', render: CompactTableFormatters.FormatJsonParse(), }, + { + label: 'Sender', + key: 'cmd.meta.sender', + variant: 'code', + width: '25%', + render: FormatLinkWrapper({ url: '/account/:value' }), + }, ]} data={innerData.node.transactions.edges.map( (edge) => edge.node as Transaction, diff --git a/packages/apps/explorer/src/components/DataRenderComponent/ExpandTruncatedField/ExpandTruncatedField.tsx b/packages/apps/explorer/src/components/DataRenderComponent/ExpandTruncatedField/ExpandTruncatedField.tsx index 20180fd117..20f833e867 100644 --- a/packages/apps/explorer/src/components/DataRenderComponent/ExpandTruncatedField/ExpandTruncatedField.tsx +++ b/packages/apps/explorer/src/components/DataRenderComponent/ExpandTruncatedField/ExpandTruncatedField.tsx @@ -2,7 +2,7 @@ import { CopyButton } from '@/components/CopyButton/CopyButton'; import { Stack, Text } from '@kadena/kode-ui'; import classNames from 'classnames'; import type { FC } from 'react'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { descriptionDetailsClass, descriptionDetailsExpandedClass, @@ -14,57 +14,12 @@ interface IProps { field: IDataRenderComponentField; } -const storageKey = 'expandedfields'; - export const ExpandTruncatedField: FC = ({ field }) => { const [isExpanded, setIsExpanded] = useState(false); - const toggleExpand = (): void => { - let storage: string[] = JSON.parse( - localStorage.getItem(storageKey) ?? '[]', - ); - - const key = field.key ? field.key : field.id ?? ''; - - if (isExpanded) { - storage = storage.filter((v) => v !== key); - } else { - storage.push(key); - } - - localStorage.setItem(storageKey, JSON.stringify(storage)); - - window.dispatchEvent(new Event(storageKey)); - setIsExpanded((v) => !isExpanded); - }; - - const checkStorage = () => { - const storage: string[] = JSON.parse( - localStorage.getItem(storageKey) ?? '[]', - ); - - setIsExpanded(storage.includes(field.key)); - }; - const storageListener = useCallback((event: StorageEvent | Event) => { - if (event.type !== storageKey && 'key' in event && event.key !== storageKey) - return; - - checkStorage(); - }, []); - - useEffect(() => { - checkStorage(); - window.addEventListener(storageKey, storageListener); - window.addEventListener('storage', storageListener); - return () => { - window.removeEventListener(storageKey, storageListener); - window.removeEventListener('storage', storageListener); - }; - }, [storageListener]); - return ( setIsExpanded(true)} as="dd" gap="xs" className={classNames(descriptionDetailsClass, { diff --git a/packages/apps/explorer/src/components/Search/NoSearchResults/NoSearchResults.tsx b/packages/apps/explorer/src/components/Search/NoSearchResults/NoSearchResults.tsx index cb18b3f75a..7fc0b77e0f 100644 --- a/packages/apps/explorer/src/components/Search/NoSearchResults/NoSearchResults.tsx +++ b/packages/apps/explorer/src/components/Search/NoSearchResults/NoSearchResults.tsx @@ -1,13 +1,13 @@ -import { Heading, Stack } from '@kadena/kode-ui'; +import { Heading, Text } from '@kadena/kode-ui'; import type { FC } from 'react'; import React from 'react'; -interface NoSearchResultsProps { +interface INoSearchResultsProps { type?: 'requestKey' | 'accountName' | 'blockhash'; value?: string; } -export const NoSearchResults: FC = ({ type, value }) => { +export const NoSearchResults: FC = ({ type, value }) => { // TODO: add buttons to navigate to other networks testnet/mainnet // Use url from router to query other networks to see if the search result is // there @@ -15,31 +15,21 @@ export const NoSearchResults: FC = ({ type, value }) => { switch (true) { case type === 'requestKey' && value !== undefined: return ( - - No search results for request key: {value} - + No search results for request key: {value} ); case type === 'accountName' && value !== undefined: return ( - + <> No search results for account: {value} - Please check the account name and try again - + Please check the network and account name and try again + ); case type === 'blockhash' && value !== undefined: - return ( - - No search results for block: {value} - - ); + return No search results for block: {value}; default: - return ( - - No search results - - ); + return No search results; } }; diff --git a/packages/apps/explorer/src/pages/[networkSlug]/event/[eventname].tsx b/packages/apps/explorer/src/pages/[networkSlug]/event/[eventname].tsx index 2c9f0f68e5..6fa5426c78 100644 --- a/packages/apps/explorer/src/pages/[networkSlug]/event/[eventname].tsx +++ b/packages/apps/explorer/src/pages/[networkSlug]/event/[eventname].tsx @@ -30,7 +30,9 @@ const Height: React.FC = () => { key={`chain${chainData.chainId}`} > {chainData.data.edges.length === 0 ? ( - + + + ) : ( { }, [loading, data, error, setIsLoading]); return ( - + {innerData && innerData.transaction ? ( <> @@ -120,7 +120,12 @@ const Transaction: React.FC = () => { ) : ( !Array.isArray(router.query.requestKey) && ( - + + + ) )} diff --git a/packages/apps/explorer/src/utils/__test__/condenseString.test.ts b/packages/apps/explorer/src/utils/__test__/condenseString.test.ts new file mode 100644 index 0000000000..a6161980bb --- /dev/null +++ b/packages/apps/explorer/src/utils/__test__/condenseString.test.ts @@ -0,0 +1,79 @@ +import { condenseStrings } from '../condenseStrings'; + +describe('condenseString', () => { + const singleWord = + 'thisisaverylongstringthatshouldbecondensedthisisaverylongstringthatshouldbecondensed'; + const multiWord = + 'thisisaverylongstringthatshouldbecond ensedthisisaverylongstringthatshouldbecondensed thisIsAshorterString'; + + describe('with default options', () => { + it('condenses a single string', () => { + expect(condenseStrings(singleWord)).toBe('thisi…ensed'); + }); + + it('condenses a string with multiple words', () => { + expect(condenseStrings(multiWord)).toBe( + 'thisi…econd ensed…ensed thisIsAshorterString', + ); + }); + }); + describe('with custom options', () => { + it('minLength 10', () => { + expect(condenseStrings(singleWord, { minLength: 10 })).toBe( + 'thisi…ensed', + ); + expect(condenseStrings(multiWord, { minLength: 10 })).toBe( + 'thisi…econd ensed…ensed thisI…tring', + ); + }); + + it('replacement "..."', () => { + expect(condenseStrings(singleWord, { replacement: '...' })).toBe( + 'thisi...ensed', + ); + expect(condenseStrings(multiWord, { replacement: '...' })).toBe( + 'thisi...econd ensed...ensed thisIsAshorterString', + ); + }); + + it('startLength 10', () => { + expect(condenseStrings(singleWord, { startLength: 10 })).toBe( + 'thisisaver…ensed', + ); + expect(condenseStrings(multiWord, { startLength: 10 })).toBe( + 'thisisaver…econd ensedthisi…ensed thisIsAshorterString', + ); + }); + + it('endLength 10', () => { + expect(condenseStrings(singleWord, { endLength: 10 })).toBe( + 'thisi…econdensed', + ); + expect(condenseStrings(multiWord, { endLength: 10 })).toBe( + 'thisi…ouldbecond ensed…econdensed thisIsAshorterString', + ); + }); + + it('all options', () => { + expect( + condenseStrings(singleWord, { + minLength: 40, + replacement: '*.*', + startLength: 5, + endLength: 20, + }), + ).toBe('thisi*.*hatshouldbecondensed'); + + expect( + condenseStrings(multiWord, { + minLength: 40, + replacement: '*.*', + startLength: 5, + endLength: 20, + }), + ).toBe( + 'thisisaverylongstringthatshouldbecond ensed*.*hatshouldbecondensed thisIsAshorterString', + ); + }); + }); +}); diff --git a/packages/apps/explorer/src/utils/condenseStrings.ts b/packages/apps/explorer/src/utils/condenseStrings.ts new file mode 100644 index 0000000000..2d43bf411b --- /dev/null +++ b/packages/apps/explorer/src/utils/condenseStrings.ts @@ -0,0 +1,43 @@ +/** + * Shortens long sections in a string by keeping the first 5 and last 5 characters. + * @param str - string to shorten + * @returns {[string, string]} - shortened string and original string + */ +export function condenseStrings( + str: string, + options?: { + minLength?: number; + replacement?: string; + startLength?: number; + endLength?: number; + }, +): string { + // set default options and override with provided options + let config; + if (options) { + config = { + ...{ minLength: 22, replacement: '…', startLength: 5, endLength: 5 }, + ...options, + }; + } else { + config = { + minLength: 22, + replacement: '…', + startLength: 5, + endLength: 5, + }; + } + + // Regex explanation: + // Split on sequences of characters that are NOT letters, digits, '_' or '-'. + const parts = str.split(/([^\w\d]+)/u); + + return parts + .map((part) => { + if (/^[\w\d]+$/u.test(part) && part.length > config.minLength) { + return `${part.slice(0, config.startLength)}${config.replacement}${part.slice(-config.endLength)}`; + } + return part; + }) + .join(''); +}