Remove clusterUrl url param

This commit is contained in:
Justin Starry 2020-06-03 18:09:05 +08:00 committed by Michael Vines
parent 03f7d28aff
commit 8b95be0ee4
7 changed files with 99 additions and 102 deletions

View File

@ -1263,6 +1263,11 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
}, },
"@react-hook/debounce": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-2.0.5.tgz",
"integrity": "sha512-WrwQ1e4vx5lxxxEpGPPliMcs6oUJ8cyEb/GL8OEUPhBW4WL8YRSDW5oPnsOqIPqhHDyQMHgMipXWgj7QVmRMKA=="
},
"@sheerun/mutationobserver-shim": { "@sheerun/mutationobserver-shim": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz",

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@react-hook/debounce": "^2.0.5",
"@solana/web3.js": "^0.56.0", "@solana/web3.js": "^0.56.0",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",

View File

@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import { Link, useLocation, useHistory } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { useDebounceCallback } from "@react-hook/debounce";
import { Location } from "history"; import { Location } from "history";
import { import {
useCluster, useCluster,
@ -9,10 +10,12 @@ import {
clusterSlug, clusterSlug,
CLUSTERS, CLUSTERS,
Cluster, Cluster,
useClusterModal useClusterModal,
useUpdateCustomUrl
} from "../providers/cluster"; } from "../providers/cluster";
import { assertUnreachable } from "../utils"; import { assertUnreachable } from "../utils";
import Overlay from "./Overlay"; import Overlay from "./Overlay";
import { useQuery } from "utils/url";
function ClusterModal() { function ClusterModal() {
const [show, setShow] = useClusterModal(); const [show, setShow] = useClusterModal();
@ -46,34 +49,35 @@ function ClusterModal() {
type InputProps = { activeSuffix: string; active: boolean }; type InputProps = { activeSuffix: string; active: boolean };
function CustomClusterInput({ activeSuffix, active }: InputProps) { function CustomClusterInput({ activeSuffix, active }: InputProps) {
const { customUrl } = useCluster(); const { customUrl } = useCluster();
const updateCustomUrl = useUpdateCustomUrl();
const [editing, setEditing] = React.useState(false); const [editing, setEditing] = React.useState(false);
const query = useQuery();
const history = useHistory(); const history = useHistory();
const location = useLocation(); const location = useLocation();
const customClass = (prefix: string) => const customClass = (prefix: string) =>
active ? `${prefix}-${activeSuffix}` : ""; active ? `${prefix}-${activeSuffix}` : "";
const clusterLocation = (location: Location, url: string) => { const clusterLocation = (location: Location) => {
const params = new URLSearchParams(location.search); if (customUrl.length > 0) query.set("cluster", "custom");
params.set("clusterUrl", url);
params.delete("cluster");
return { return {
...location, ...location,
search: params.toString() search: query.toString()
}; };
}; };
const updateCustomUrl = React.useCallback( const onUrlInput = useDebounceCallback((url: string) => {
(url: string) => { updateCustomUrl(url);
history.push(clusterLocation(location, url)); if (url.length > 0) {
}, query.set("cluster", "custom");
[history, location] history.push({ ...location, search: query.toString() });
); }
}, 500);
const inputTextClass = editing ? "" : "text-muted"; const inputTextClass = editing ? "" : "text-muted";
return ( return (
<Link <Link
to={location => clusterLocation(location, customUrl)} to={location => clusterLocation(location)}
className="btn input-group input-group-merge p-0" className="btn input-group input-group-merge p-0"
> >
<input <input
@ -84,7 +88,7 @@ function CustomClusterInput({ activeSuffix, active }: InputProps) {
)}`} )}`}
onFocus={() => setEditing(true)} onFocus={() => setEditing(true)}
onBlur={() => setEditing(false)} onBlur={() => setEditing(false)}
onInput={e => updateCustomUrl(e.currentTarget.value)} onInput={e => onUrlInput(e.currentTarget.value)}
/> />
<div className="input-group-prepend"> <div className="input-group-prepend">
<div className={`input-group-text pr-0 ${customClass("border")}`}> <div className={`input-group-text pr-0 ${customClass("border")}`}>
@ -133,14 +137,10 @@ function ClusterToggle() {
const clusterLocation = (location: Location) => { const clusterLocation = (location: Location) => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search);
const slug = clusterSlug(net); const slug = clusterSlug(net);
if (slug && slug !== "mainnet-beta") { if (slug !== "mainnet-beta") {
params.set("cluster", slug); params.set("cluster", slug);
params.delete("clusterUrl");
} else { } else {
params.delete("cluster"); params.delete("cluster");
if (slug === "mainnet-beta") {
params.delete("clusterUrl");
}
} }
return { return {
...location, ...location,

View File

@ -3,10 +3,9 @@ import {
useFetchTransactionStatus, useFetchTransactionStatus,
useTransactionStatus, useTransactionStatus,
useTransactionDetails, useTransactionDetails,
useDetailsDispatch,
FetchStatus FetchStatus
} from "../providers/transactions"; } from "../providers/transactions";
import { fetchDetails } from "providers/transactions/details"; import { useFetchTransactionDetails } from "providers/transactions/details";
import { useCluster, useClusterModal } from "providers/cluster"; import { useCluster, useClusterModal } from "providers/cluster";
import { import {
TransactionSignature, TransactionSignature,
@ -206,12 +205,11 @@ function StatusCard({ signature }: Props) {
function AccountsCard({ signature }: Props) { function AccountsCard({ signature }: Props) {
const details = useTransactionDetails(signature); const details = useTransactionDetails(signature);
const dispatch = useDetailsDispatch();
const { url } = useCluster();
const fetchStatus = useFetchTransactionStatus(); const fetchStatus = useFetchTransactionStatus();
const fetchDetails = useFetchTransactionDetails();
const refreshStatus = () => fetchStatus(signature); const refreshStatus = () => fetchStatus(signature);
const refreshDetails = () => fetchDetails(dispatch, signature, url); const refreshDetails = () => fetchDetails(signature);
const transaction = details?.transaction?.transaction; const transaction = details?.transaction?.transaction;
const message = React.useMemo(() => { const message = React.useMemo(() => {
return transaction?.compileMessage(); return transaction?.compileMessage();
@ -308,9 +306,8 @@ function AccountsCard({ signature }: Props) {
function InstructionsSection({ signature }: Props) { function InstructionsSection({ signature }: Props) {
const status = useTransactionStatus(signature); const status = useTransactionStatus(signature);
const details = useTransactionDetails(signature); const details = useTransactionDetails(signature);
const dispatch = useDetailsDispatch(); const fetchDetails = useFetchTransactionDetails();
const { url } = useCluster(); const refreshDetails = () => fetchDetails(signature);
const refreshDetails = () => fetchDetails(dispatch, signature, url);
if (!status || !status.info || !details || !details.transaction) return null; if (!status || !status.info || !details || !details.transaction) return null;

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { clusterApiUrl, Connection } from "@solana/web3.js"; import { clusterApiUrl, Connection } from "@solana/web3.js";
import { useQuery } from "../utils/url"; import { useQuery } from "../utils/url";
import { useHistory, useLocation } from "react-router-dom";
export enum ClusterStatus { export enum ClusterStatus {
Connected, Connected,
@ -22,7 +23,7 @@ export const CLUSTERS = [
Cluster.Custom Cluster.Custom
]; ];
export function clusterSlug(cluster: Cluster): string | undefined { export function clusterSlug(cluster: Cluster): string {
switch (cluster) { switch (cluster) {
case Cluster.MainnetBeta: case Cluster.MainnetBeta:
return "mainnet-beta"; return "mainnet-beta";
@ -31,7 +32,7 @@ export function clusterSlug(cluster: Cluster): string | undefined {
case Cluster.Devnet: case Cluster.Devnet:
return "devnet"; return "devnet";
case Cluster.Custom: case Cluster.Custom:
return undefined; return "custom";
} }
} }
@ -52,8 +53,20 @@ export const MAINNET_BETA_URL = clusterApiUrl("mainnet-beta");
export const TESTNET_URL = clusterApiUrl("testnet"); export const TESTNET_URL = clusterApiUrl("testnet");
export const DEVNET_URL = clusterApiUrl("devnet"); export const DEVNET_URL = clusterApiUrl("devnet");
export function clusterUrl(cluster: Cluster, customUrl: string): string {
switch (cluster) {
case Cluster.Devnet:
return DEVNET_URL;
case Cluster.MainnetBeta:
return MAINNET_BETA_URL;
case Cluster.Testnet:
return TESTNET_URL;
case Cluster.Custom:
return customUrl;
}
}
export const DEFAULT_CLUSTER = Cluster.MainnetBeta; export const DEFAULT_CLUSTER = Cluster.MainnetBeta;
export const DEFAULT_CUSTOM_URL = "http://localhost:8899";
interface State { interface State {
cluster: Cluster; cluster: Cluster;
@ -88,51 +101,19 @@ function clusterReducer(state: State, action: Action): State {
} }
} }
function parseQuery( function parseQuery(query: URLSearchParams): Cluster {
query: URLSearchParams
): { cluster: Cluster; customUrl: string } {
const clusterParam = query.get("cluster"); const clusterParam = query.get("cluster");
const clusterUrlParam = query.get("clusterUrl");
let cluster;
let customUrl = DEFAULT_CUSTOM_URL;
switch (clusterUrlParam) {
case MAINNET_BETA_URL:
cluster = Cluster.MainnetBeta;
break;
case DEVNET_URL:
cluster = Cluster.Devnet;
break;
case TESTNET_URL:
cluster = Cluster.Testnet;
break;
}
switch (clusterParam) { switch (clusterParam) {
case "mainnet-beta": case "custom":
cluster = Cluster.MainnetBeta; return Cluster.Custom;
break;
case "devnet": case "devnet":
cluster = Cluster.Devnet; return Cluster.Devnet;
break;
case "testnet": case "testnet":
cluster = Cluster.Testnet; return Cluster.Testnet;
break; case "mainnet-beta":
default:
return Cluster.MainnetBeta;
} }
if (!cluster) {
if (!clusterUrlParam) {
cluster = DEFAULT_CLUSTER;
} else {
cluster = Cluster.Custom;
customUrl = clusterUrlParam;
}
}
return {
cluster,
customUrl
};
} }
type SetShowModal = React.Dispatch<React.SetStateAction<boolean>>; type SetShowModal = React.Dispatch<React.SetStateAction<boolean>>;
@ -146,16 +127,28 @@ type ClusterProviderProps = { children: React.ReactNode };
export function ClusterProvider({ children }: ClusterProviderProps) { export function ClusterProvider({ children }: ClusterProviderProps) {
const [state, dispatch] = React.useReducer(clusterReducer, { const [state, dispatch] = React.useReducer(clusterReducer, {
cluster: DEFAULT_CLUSTER, cluster: DEFAULT_CLUSTER,
customUrl: DEFAULT_CUSTOM_URL, customUrl: "",
status: ClusterStatus.Connecting status: ClusterStatus.Connecting
}); });
const [showModal, setShowModal] = React.useState(false); const [showModal, setShowModal] = React.useState(false);
const { cluster, customUrl } = parseQuery(useQuery()); const query = useQuery();
const cluster = parseQuery(query);
const history = useHistory();
const location = useLocation();
// Reconnect to cluster when it changes // Reconnect to cluster when params change
React.useEffect(() => { React.useEffect(() => {
updateCluster(dispatch, cluster, customUrl); if (cluster === Cluster.Custom) {
}, [cluster, customUrl]); // Remove cluster param if custom url has not been set
if (state.customUrl.length === 0) {
query.delete("cluster");
history.push({ ...location, search: query.toString() });
return;
}
}
updateCluster(dispatch, cluster, state.customUrl);
}, [cluster, state.customUrl]); // eslint-disable-line react-hooks/exhaustive-deps
return ( return (
<StateContext.Provider value={state}> <StateContext.Provider value={state}>
@ -168,19 +161,6 @@ export function ClusterProvider({ children }: ClusterProviderProps) {
); );
} }
export function clusterUrl(cluster: Cluster, customUrl: string): string {
switch (cluster) {
case Cluster.Devnet:
return DEVNET_URL;
case Cluster.MainnetBeta:
return MAINNET_BETA_URL;
case Cluster.Testnet:
return TESTNET_URL;
case Cluster.Custom:
return customUrl;
}
}
async function updateCluster( async function updateCluster(
dispatch: Dispatch, dispatch: Dispatch,
cluster: Cluster, cluster: Cluster,
@ -207,6 +187,17 @@ async function updateCluster(
} }
} }
export function useUpdateCustomUrl() {
const dispatch = React.useContext(DispatchContext);
if (!dispatch) {
throw new Error(`useUpdateCustomUrl must be used within a ClusterProvider`);
}
return (customUrl: string) => {
updateCluster(dispatch, Cluster.Custom, customUrl);
};
}
export function useCluster() { export function useCluster() {
const context = React.useContext(StateContext); const context = React.useContext(StateContext);
if (!context) { if (!context) {

View File

@ -128,7 +128,7 @@ export function DetailsProvider({ children }: DetailsProviderProps) {
); );
} }
export async function fetchDetails( async function fetchDetails(
dispatch: Dispatch, dispatch: Dispatch,
signature: TransactionSignature, signature: TransactionSignature,
url: string url: string
@ -151,3 +151,17 @@ export async function fetchDetails(
} }
dispatch({ type: ActionType.Update, fetchStatus, signature, transaction }); dispatch({ type: ActionType.Update, fetchStatus, signature, transaction });
} }
export function useFetchTransactionDetails() {
const dispatch = React.useContext(DispatchContext);
if (!dispatch) {
throw new Error(
`useFetchTransactionDetails must be used within a TransactionsProvider`
);
}
const { url } = useCluster();
return (signature: TransactionSignature) => {
url && fetchDetails(dispatch, signature, url);
};
}

View File

@ -12,8 +12,7 @@ import { useQuery } from "../../utils/url";
import { useCluster, Cluster, ClusterStatus } from "../cluster"; import { useCluster, Cluster, ClusterStatus } from "../cluster";
import { import {
DetailsProvider, DetailsProvider,
StateContext as DetailsStateContext, StateContext as DetailsStateContext
DispatchContext as DetailsDispatchContext
} from "./details"; } from "./details";
import base58 from "bs58"; import base58 from "bs58";
import { useFetchAccountInfo } from "../accounts"; import { useFetchAccountInfo } from "../accounts";
@ -308,16 +307,6 @@ export function useTransactionDetails(signature: TransactionSignature) {
return context[signature]; return context[signature];
} }
export function useDetailsDispatch() {
const context = React.useContext(DetailsDispatchContext);
if (!context) {
throw new Error(
`useDetailsDispatch must be used within a TransactionsProvider`
);
}
return context;
}
export function useFetchTransactionStatus() { export function useFetchTransactionStatus() {
const dispatch = React.useContext(DispatchContext); const dispatch = React.useContext(DispatchContext);
if (!dispatch) { if (!dispatch) {