Skip to content

Commit

Permalink
Add network switch without page reload (#1423)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitayutanov authored Oct 5, 2023
1 parent e8b18e7 commit b7166c0
Show file tree
Hide file tree
Showing 60 changed files with 373 additions and 451 deletions.
2 changes: 1 addition & 1 deletion idea/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"@gear-js/api": "0.33.6",
"@gear-js/react-hooks": "0.6.10",
"@gear-js/react-hooks": "0.8.0",
"@gear-js/ui": "0.5.19",
"@hcaptcha/react-hcaptcha": "^1.4.4",
"@mantine/form": "^5.4.0",
Expand Down
20 changes: 10 additions & 10 deletions idea/frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ErrorBoundary } from 'react-error-boundary';
import { useAccount, useApi } from '@gear-js/react-hooks';
import 'simplebar-react/dist/simplebar.min.css';

import { useApp, useChain, useEventSubscriptions, useMobileDisclaimer } from 'hooks';
import { useChain, useEventSubscriptions, useMobileDisclaimer } from 'hooks';
import { Menu } from 'widgets/menu';
import { Header } from 'widgets/header';
import { Footer } from 'widgets/footer';
Expand All @@ -13,12 +13,12 @@ import { Routing } from 'pages';
import { LocalStorage, NODE_ADRESS_URL_PARAM } from 'shared/config';
import { Loader } from 'shared/ui/loader';
import { ErrorFallback } from 'shared/ui/errorFallback';
import { INITIAL_ENDPOINT } from 'features/api';

import { withProviders } from './providers';
import './App.scss';

const App = withProviders(() => {
const { nodeAddress } = useApp();
const { pathname } = useLocation();
const [searchParams, setSearchParams] = useSearchParams();

Expand All @@ -34,17 +34,17 @@ const App = withProviders(() => {
useEffect(() => {
const urlNodeAddress = searchParams.get(NODE_ADRESS_URL_PARAM);

if (!urlNodeAddress) {
searchParams.set(NODE_ADRESS_URL_PARAM, nodeAddress);
setSearchParams(searchParams, { replace: true });
}
if (urlNodeAddress) return;

searchParams.set(NODE_ADRESS_URL_PARAM, isApiReady ? api.provider.endpoint : INITIAL_ENDPOINT);
setSearchParams(searchParams, { replace: true });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);
}, [isApiReady, searchParams]);

useEffect(() => {
if (isApiReady) {
localStorage.setItem(LocalStorage.Genesis, api.genesisHash.toHex());
}
if (!isApiReady) return;

localStorage.setItem(LocalStorage.Genesis, api.genesisHash.toHex());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isApiReady]);

Expand Down
10 changes: 4 additions & 6 deletions idea/frontend/src/app/providers/api/Provider.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { ProviderProps, ApiProvider as GearApiProvider } from '@gear-js/react-hooks';

import { useApp } from 'hooks';
import { INITIAL_ENDPOINT } from 'features/api';

const ApiProvider = ({ children }: ProviderProps) => {
const { nodeAddress } = useApp();

return <GearApiProvider providerAddress={nodeAddress}>{children}</GearApiProvider>;
};
const ApiProvider = ({ children }: ProviderProps) => (
<GearApiProvider initialArgs={{ endpoint: INITIAL_ENDPOINT }}>{children}</GearApiProvider>
);

export { ApiProvider };
7 changes: 0 additions & 7 deletions idea/frontend/src/app/providers/app/Context.ts

This file was deleted.

21 changes: 0 additions & 21 deletions idea/frontend/src/app/providers/app/Provider.tsx

This file was deleted.

4 changes: 0 additions & 4 deletions idea/frontend/src/app/providers/app/index.ts

This file was deleted.

5 changes: 0 additions & 5 deletions idea/frontend/src/app/providers/app/types.ts

This file was deleted.

25 changes: 11 additions & 14 deletions idea/frontend/src/app/providers/blocks/Provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode, useState, useEffect } from 'react';
import { Header } from '@polkadot/types/interfaces';
import { useApi } from '@gear-js/react-hooks';
import { Header } from '@polkadot/types/interfaces';

import { IChainBlock } from 'entities/chainBlock';

Expand All @@ -12,7 +12,7 @@ type Props = {
};

const BlocksProvider = ({ children }: Props) => {
const { api } = useApi();
const { api, isApiReady } = useApi();
const [blocks, setBlocks] = useState<IChainBlock[]>([]);

const updateBlocks = (block: IChainBlock) =>
Expand All @@ -22,25 +22,22 @@ const BlocksProvider = ({ children }: Props) => {
return [block, ...blocksTail];
});

const handleSubscription = (header: Header) =>
api.blocks
.getBlockTimestamp(header.hash)
.then((timestamp) => getTime(timestamp.toNumber()))
.then((time) => getBlock(header, time))
.then(updateBlocks);

useEffect(() => {
if (!api) {
return;
}
if (!isApiReady) return;

const unsub = api.gearEvents.subscribeToNewBlocks(handleSubscription);
const unsub = api.blocks.subscribeNewHeads((header: Header) =>
api.blocks
.getBlockTimestamp(header.hash)
.then((timestamp) => getTime(timestamp.toNumber()))
.then((time) => getBlock(header, time))
.then((result) => updateBlocks(result)),
);

return () => {
unsub.then((unsubscribe) => unsubscribe());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [api]);
}, [isApiReady]);

return <BlocksContext.Provider value={blocks}>{children}</BlocksContext.Provider>;
};
Expand Down
32 changes: 16 additions & 16 deletions idea/frontend/src/app/providers/chain/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,30 @@ const ChainProvider = ({ children }: ProviderProps) => {

const [isDevChain, setIsDevChain] = useState<boolean>();
const [isTestBalanceAvailable, setIsTestBalanceAvailable] = useState<boolean>();

const isChainRequestReady = isDevChain !== undefined && isTestBalanceAvailable !== undefined;

useEffect(() => {
if (genesis) {
const apiRequest = new RPCService();
setIsDevChain(undefined);

apiRequest.callRPC<boolean>(RpcMethods.NetworkData, { genesis }).then(({ result }) => setIsDevChain(!result));
}
if (!genesis) return;

new RPCService().callRPC<boolean>(RpcMethods.NetworkData, { genesis }).then(({ result }) => setIsDevChain(!result));
}, [genesis]);

useEffect(() => {
if (isDevChain !== undefined) {
if (isDevChain) {
setIsTestBalanceAvailable(true);
} else {
const apiRequest = new RPCService();

apiRequest
.callRPC<boolean>(RpcMethods.TestBalanceAvailable, { genesis })
.then(({ result }) => setIsTestBalanceAvailable(result));
}
setIsTestBalanceAvailable(undefined);

if (isDevChain === undefined) return;

if (isDevChain) {
setIsTestBalanceAvailable(true);
} else {
new RPCService()
.callRPC<boolean>(RpcMethods.TestBalanceAvailable, { genesis })
.then(({ result }) => setIsTestBalanceAvailable(result));
}
}, [isDevChain, genesis]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDevChain]);

return <Provider value={{ isDevChain, isTestBalanceAvailable, isChainRequestReady }}>{children}</Provider>;
};
Expand Down
37 changes: 15 additions & 22 deletions idea/frontend/src/app/providers/events/Provider.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,36 @@
import { useState, useEffect } from 'react';
import { useApi, ProviderProps } from '@gear-js/react-hooks';

import { EventRecords, IdeaEvent, Section } from 'features/explorer';
import { IdeaEvent, Section } from 'features/explorer';

import { UnsubscribePromise } from '@polkadot/api/types';
import { EventsContext } from './Context';

const EventsProvider = ({ children }: ProviderProps) => {
const { api } = useApi();
const { api, isApiReady } = useApi();
const [events, setEvents] = useState<IdeaEvent[]>();

const subscribeToEvents = () =>
api.query.system.events(async (records: EventRecords) => {
const { createdAtHash } = records;

if (createdAtHash) {
const blockNumber = await api.blocks.getBlockNumber(createdAtHash);
useEffect(() => {
if (!isApiReady) return;

const newEvents = records
.map(({ event }) => new IdeaEvent(event, blockNumber))
.filter(({ section }) => section !== Section.System)
.reverse();
const unsub = api.query.system.events(async (records) => {
const { createdAtHash } = records;
if (!createdAtHash) return;

setEvents((prevEvents) => (prevEvents ? [...newEvents, ...prevEvents] : newEvents));
}
}) as unknown as UnsubscribePromise;
const blockNumber = await api.blocks.getBlockNumber(createdAtHash);

useEffect(() => {
if (!api) {
return;
}
const newEvents = records
.map(({ event }) => new IdeaEvent(event, blockNumber))
.filter(({ section }) => section !== Section.System)
.reverse();

const unsub = subscribeToEvents();
setEvents((prevEvents) => (prevEvents ? [...newEvents, ...prevEvents] : newEvents));
});

return () => {
unsub.then((unsubscribe) => unsubscribe());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [api]);
}, [isApiReady]);

return <EventsContext.Provider value={events}>{children}</EventsContext.Provider>;
};
Expand Down
2 changes: 0 additions & 2 deletions idea/frontend/src/app/providers/withProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ComponentType } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { AccountProvider } from '@gear-js/react-hooks';

import { AppProvider } from './app';
import { ApiProvider } from './api';
import { AlertProvider } from './alert';
import { BlocksProvider } from './blocks';
Expand All @@ -13,7 +12,6 @@ import { OnboardingProvider } from './onboarding';

const providers = [
BrowserRouter,
AppProvider,
AlertProvider,
ApiProvider,
AccountProvider,
Expand Down
7 changes: 7 additions & 0 deletions idea/frontend/src/features/api/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LocalStorage, NODE_ADDRESS } from 'shared/config';

import { getNodeAddressFromUrl } from './utils';

const INITIAL_ENDPOINT = getNodeAddressFromUrl() || (localStorage[LocalStorage.Node] as string | null) || NODE_ADDRESS;

export { INITIAL_ENDPOINT };
3 changes: 3 additions & 0 deletions idea/frontend/src/features/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { INITIAL_ENDPOINT } from './consts';

export { INITIAL_ENDPOINT };
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { isNodeAddressValid } from 'shared/helpers';
import { NODE_ADRESS_URL_PARAM } from 'shared/config';
import { isNodeAddressValid } from 'shared/helpers';

const getNodeAddressFromUrl = () => {
const searchParams = new URLSearchParams(window.location.search);
const nodeAddress = searchParams.get(NODE_ADRESS_URL_PARAM);

if (nodeAddress && isNodeAddressValid(nodeAddress)) {
return nodeAddress;
}
if (nodeAddress && isNodeAddressValid(nodeAddress)) return nodeAddress;
};

export { getNodeAddressFromUrl };
36 changes: 19 additions & 17 deletions idea/frontend/src/features/explorer/ui/block/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import { MainTable } from '../main-table';
import { System } from '../system';
import styles from './block.module.scss';

type Params = { blockId: string };
type Params = {
blockId: string;
};

const Block = () => {
const { api } = useApi();
const { api, isApiReady } = useApi();

const { blockId } = useParams() as Params;

Expand All @@ -30,21 +32,21 @@ const Block = () => {
};

useEffect(() => {
if (api) {
resetState();

const isBlockHash = isHex(blockId);
const id = isBlockHash ? (blockId as `0x${string}`) : Number(blockId);

api.blocks
.get(id)
.then(({ block: newBlock }) => {
api.blocks.getEvents(newBlock.hash).then(setEventRecords);
setBlock(newBlock);
})
.catch(({ message }: Error) => setError(message));
}
}, [api, blockId]);
if (!isApiReady) return;

resetState();

const id = isHex(blockId) ? blockId : Number(blockId);

api.blocks
.get(id)
.then((result) => {
api.blocks.getEvents(result.block.hash).then((recordsResult) => setEventRecords(recordsResult));
setBlock(result.block);
})
.catch(({ message }: Error) => setError(message));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isApiReady, blockId]);

return (
<div className={styles.block}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { IProgram, useProgramStatus } from 'features/program';
import { isState, useMetadata } from 'features/metadata';

function useLocalProgram() {
const { api } = useApi();
const genesis = api?.genesisHash.toHex();
const { api, isApiReady } = useApi();

const { getMetadata } = useMetadata();
const { getProgramStatus } = useProgramStatus();

const getChainProgram = async (id: HexString) => {
if (!isApiReady) return Promise.reject(new Error('API is not initialized'));

const name = id;
const status = await getProgramStatus(id);

Expand Down Expand Up @@ -52,10 +53,12 @@ function useLocalProgram() {
};

const getLocalProgram = async (id: HexString) => {
if (!isApiReady) return Promise.reject(new Error('API is not initialized'));

const localForageProgram = await PROGRAMS_LOCAL_FORAGE.getItem<IProgram>(id);

const isProgramInChain = id === localForageProgram?.id;
const isProgramFromChain = genesis === localForageProgram?.genesis;
const isProgramFromChain = api.genesisHash.toHex() === localForageProgram?.genesis;

return isProgramInChain && isProgramFromChain ? localForageProgram : getChainProgram(id);
};
Expand Down
Loading

0 comments on commit b7166c0

Please sign in to comment.