Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the search bar (and add bech32 search) #60

Merged
merged 5 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.zilliqa.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ ignore changes to them, they are generated by `npm start` as well as

Be warned! If you use `vite` directly, you may end up with analysis
errors due to their absence.

## Starting for development

.. because I keep forgetting!

```
export VITE_ERIGON_URL=<url>
npm run assets-start
npm start
```
117 changes: 116 additions & 1 deletion src/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { faQrcode } from "@fortawesome/free-solid-svg-icons";
import { faQrcode, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FC, lazy, memo, useContext, useState } from "react";
import { Link } from "react-router-dom";
import PriceBox from "./PriceBox";
import SourcifyMenu from "./SourcifyMenu";
import InlineCode from "./components/InlineCode";
import { useGenericSearch } from "./search/search";
import { RuntimeContext } from "./useRuntime";
// @ts-expect-error
Expand All @@ -12,10 +13,13 @@ import Otter from "./otter.png?w=128&h=128&webp";
const CameraScanner = lazy(() => import("./search/CameraScanner"));
type HeaderProps = { sourcifyPresent: boolean };

// Should really move out to utils

const Header: FC<HeaderProps> = ({ sourcifyPresent }) => {
const { config, provider } = useContext(RuntimeContext);
const [searchRef, handleChange, handleSubmit] = useGenericSearch();
const [isScanning, setScanning] = useState<boolean>(false);
const [isHelpOpen, setHelpOpen] = useState<boolean>(false);

return (
<>
Expand Down Expand Up @@ -79,6 +83,14 @@ const Header: FC<HeaderProps> = ({ sourcifyPresent }) => {
>
<FontAwesomeIcon icon={faQrcode} />
</button>
<button
className="border bg-skin-button-fill px-2 py-1 text-sm text-skin-button hover:bg-skin-button-hover-fill focus:outline-none"
type="button"
onClick={() => setHelpOpen(true)}
title="Help with searching"
>
<FontAwesomeIcon icon={faQuestionCircle} />
</button>
<button
className="rounded-r border-b border-r border-t bg-skin-button-fill px-2 py-1 text-sm text-skin-button hover:bg-skin-button-hover-fill focus:outline-none"
type="submit"
Expand All @@ -91,6 +103,109 @@ const Header: FC<HeaderProps> = ({ sourcifyPresent }) => {
</div>
</div>
</div>
{isHelpOpen && (
<div
className="fixed inset-0 z-10 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"
></div>

<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
&#8203;
</span>

<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3
className="text-lg leading-6 font-medium text-gray-900"
id="modal-title"
>
Help Information
</h3>
<div className="mt-2">
<span className="text-sm text-gray-500">
<p>Search terms are interpreted as..</p>
<br />
<ul className="list-disc list-inside mb-6">
<li>
{" "}
A bech32 address if it is in the right format.
</li>
<li>
{" "}
An address if we can (right length, starts with{" "}
<InlineCode>0x</InlineCode> or{" "}
<InlineCode>zil1</InlineCode>).
</li>
<li>
{" "}
If a 32-character hex string we'll try to search as
a transaction id
</li>
<li>
{" "}
If a &gt; 40 character hex string, we'll think it's
probably an address with leading 0s.
</li>
<li>
{" "}
Then we'll attempt an{" "}
<InlineCode>BigInt</InlineCode> and try to find a
block number
</li>
<li>
{" "}
Terms starting with <InlineCode>#</InlineCode> are
treated as a DS block number for ZQ1
JamesHinshelwood marked this conversation as resolved.
Show resolved Hide resolved
</li>
<li>
{" "}
Terms like{" "}
<InlineCode>epoch:&lt;number&gt;</InlineCode> are
epochs.
</li>
<li>
{" "}
Terms like{" "}
<InlineCode>
validator:&lt;number&gt;
</InlineCode>{" "}
are validator searches.
</li>
</ul>
<p>
If the search term does not match any of those rules,
we interpret it as an ENS name.
</p>
</span>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => setHelpOpen(false)}
>
Close
</button>
</div>
</div>
</div>
</div>
)}
</>
);
};
Expand Down
13 changes: 13 additions & 0 deletions src/components/InlineCode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";

type InlineCodeProps = React.PropsWithChildren<{
children: React.ReactNode;
}>;

const InlineCode: React.FC<InlineCodeProps> = ({ children }) => (
<code className="px-1 py-0.5 rounded bg-gray-100 text-gray-800 font-mono text-sm">
{children}
</code>
);

export default React.memo(InlineCode);
42 changes: 30 additions & 12 deletions src/search/search.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { fromBech32Address } from "@zilliqa-js/crypto";
import {
JsonRpcApiProvider,
TransactionReceiptParams,
Expand Down Expand Up @@ -247,6 +248,24 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
maybeIndex = q.substring(sepIndex + 1);
}

// Tx hash?
if (isHexString(q, 32)) {
console.log(`search: this looks like a txn hash - ${q}`);
navigate(`/tx/${q}`);
return;
} else if (isHexString(`0x${q}`, 32)) {
navigate(`/tx/0x${q}`);
return;
}

// Zilliqa address?
try {
maybeAddress = fromBech32Address(maybeAddress);
console.log(`search: bech32 address to base16 - ${maybeAddress}`);
} catch (e) {
console.log(`search: Not a bech32 address`);
}

// The type checker is convinced that ethers:isAddress() will never say that a string > 40 characters
// long is not an address. I'm not sure why...
if (!isAddress(maybeAddress)) {
Expand All @@ -266,6 +285,7 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {

// Plain address?
if (isAddress(maybeAddress)) {
console.log(`search: maybeAddress ${maybeAddress} is an address ..`);
navigate(
`/address/${maybeAddress}${
maybeIndex !== "" ? `?nonce=${maybeIndex}` : ""
Expand All @@ -274,20 +294,14 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
return;
}

// Tx hash?
if (isHexString(q, 32)) {
navigate(`/tx/${q}`);
return;
} else if (isHexString(`0x${q}`, 32)) {
navigate(`/tx/0x${q}`);
return;
}

// Block number?
// If the number here is very large, parseInt() will return an fp number which
// will cause errors, so ..
try {
const blockNumber = BigInt(q);
let toParse = q;
console.log(`search: try to parse ${toParse} as a block number`);
const blockNumber = BigInt(toParse);
console.log(`search: ${toParse} Parses as a block number ${blockNumber}`);
navigate(`/block/${blockNumber.toString()}`);
return;
} catch (e) {
Expand All @@ -298,6 +312,7 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
if (q.charAt(0) === "#") {
const dsBlockNumber = parseInt(q.substring(1));
if (!isNaN(dsBlockNumber)) {
console.log(`search: # ${dsBlockNumber} - it's a ds block number`);
navigate(`/dsblock/${dsBlockNumber}`);
return;
}
Expand All @@ -308,6 +323,7 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
const mayBeEpoch = q.substring(6);
const epoch = parseInt(mayBeEpoch);
if (!isNaN(epoch)) {
console.log(`search: epoch: ${epoch}`);
navigate(`/epoch/${epoch}`);
return;
}
Expand All @@ -318,17 +334,18 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
const mayBeSlot = q.substring(5);
const slot = parseInt(mayBeSlot);
if (!isNaN(slot)) {
console.log(`search: slot: ${slot}`);
navigate(`/slot/${slot}`);
return;
}
}

// Validator?
if (q.startsWith("validator:")) {
if (q.startsWith("search - validator:")) {
const mayBeValidator = q.substring(10);

// Validator by index
if (mayBeValidator.match(/^\d+$/)) {
console.log(`search: validator: ${mayBeValidator}`);
const validatorIndex = parseInt(mayBeValidator);
navigate(`/validator/${validatorIndex}`);
return;
Expand All @@ -342,6 +359,7 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
}

// Assume it is an ENS name
console.log(`search: no match: assuming ${maybeAddress} is an ENS name`);
navigate(
`/address/${maybeAddress}${
maybeIndex !== "" ? `?nonce=${maybeIndex}` : ""
Expand Down