diff --git a/src/components/RuntimeMetadataLoader.tsx b/src/components/RuntimeMetadataLoader.tsx
new file mode 100644
index 00000000..1d21e5f2
--- /dev/null
+++ b/src/components/RuntimeMetadataLoader.tsx
@@ -0,0 +1,151 @@
+/** @jsxImportSource @emotion/react */
+import { LinearProgress } from "@mui/material";
+import { css, Theme } from "@emotion/react";
+
+import { ReactComponent as Logo } from "../assets/calamar-logo-export-05.svg";
+import Background from "../assets/main-screen-bgr.svg";
+
+import { Footer } from "./Footer";
+
+import { usePreloadRuntimeMetadata } from "../hooks/usePreloadRuntimeMetadata";
+import { Outlet } from "react-router-dom";
+
+const containerStyle = (theme: Theme) => css`
+ --content-min-height: 900px;
+
+ width: 100%;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+
+ ${theme.breakpoints.up("sm")} {
+ --content-min-height: 1000px;
+ }
+
+ ${theme.breakpoints.up("md")} {
+ --content-min-height: 1100px;
+ }
+
+ ${theme.breakpoints.up("lg")} {
+ --content-min-height: 1200px;
+ }
+
+ ${theme.breakpoints.up("xl")} {
+ --content-min-height: 1300px;
+ }
+`;
+
+const contentStyle = css`
+ position: relative;
+ flex: 1 1 auto;
+ min-height: var(--content-min-height);
+`;
+
+const backgroundStyle = css`
+ position: absolute;
+ top: 0;
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 100vh;
+ z-index: -1;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: var(--content-min-height);
+ background-color: white;
+ background-position: center bottom;
+ background-size: 100% auto;
+ background-repeat: no-repeat;
+ background-image: url(${Background});
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: var(--content-min-height);
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #9af0f7;
+ }
+`;
+
+const logoStyle = css`
+ width: 420px;
+ margin: 40px auto;
+ display: block;
+ max-width: 100%;
+`;
+
+const subtitleStyle = (theme: Theme) => css`
+ position: relative;
+ top: -100px;
+ padding: 0 16px;
+ font-size: 16px;
+ text-align: center;
+
+ ${theme.breakpoints.down("sm")} {
+ top: -70px;
+ }
+`;
+
+const footerStyle = css`
+ flex: 0 0 auto;
+
+ > div {
+ max-width: 1000px;
+ }
+`;
+
+const metadatLoadingStyle = css`
+ max-width: 500px;
+ margin: 0 auto;
+ padding: 0 16px;
+
+ text-align: center;
+`;
+
+const metadataProgressStyle = css`
+ margin-bottom: 16px;
+ height: 8px;
+
+ border-radius: 4px;
+ background-color: #e1fbfd;
+
+ .MuiLinearProgress-bar {
+ background-color: #7acbdd;
+ }
+`;
+
+export const RuntimeMetadataLoader = () => {
+ const metadataPreload = usePreloadRuntimeMetadata();
+
+ if (metadataPreload.loading) {
+ return (
+
+
+
+
+
Block explorer for Polkadot & Kusama ecosystem
+
+
+ Loading latest runtime metadata ...
+
+
+
+
+ );
+ }
+
+ return ;
+};
diff --git a/src/hooks/usePreloadRuntimeMetadata.ts b/src/hooks/usePreloadRuntimeMetadata.ts
new file mode 100644
index 00000000..266ff3b9
--- /dev/null
+++ b/src/hooks/usePreloadRuntimeMetadata.ts
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react";
+import { getNetworks } from "../services/networksService";
+import { getLatestRuntimeSpecVersion } from "../services/runtimeSpecService";
+import { loadRuntimeMetadata } from "../services/runtimeMetadataService";
+
+export function usePreloadRuntimeMetadata() {
+ const [progress, setProgress] = useState(localStorage.getItem("runtime-metadata-preloaded") ? 100 : 0);
+
+ useEffect(() => {
+ Promise.allSettled(getNetworks().map(async (it) => {
+ try {
+ const specVersion = await getLatestRuntimeSpecVersion(it.name);
+ await loadRuntimeMetadata(it.name, specVersion);
+ } catch (e) {
+ // pass
+ }
+
+ setProgress(prev => prev + 100 / getNetworks().length);
+ })).then(() => {
+ setProgress(100);
+ localStorage.setItem("runtime-metadata-preloaded", "true");
+ });
+ }, []);
+
+ return {
+ loading: progress < 100,
+ progress
+ };
+}
diff --git a/src/router.tsx b/src/router.tsx
index 6563fef3..d3f79af0 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -1,7 +1,8 @@
import { LoaderFunctionArgs, Navigate, RouteObject, createBrowserRouter, redirect } from "react-router-dom";
import { ResultLayout } from "./components/ResultLayout";
-import { getNetwork } from "./services/networksService";
+import { RuntimeMetadataLoader } from "./components/RuntimeMetadataLoader";
+
import { encodeAddress } from "./utils/address";
import { AccountPage } from "./screens/account";
@@ -20,6 +21,7 @@ import { simplifyCallId } from "./services/callsService";
import { simplifyBlockId } from "./services/blocksService";
import { simplifyExtrinsicId } from "./services/extrinsicsService";
import { simplifyEventId } from "./services/eventsService";
+import { getNetwork } from "./services/networksService";
import { normalizeCallName, normalizeConstantName, normalizeErrorName, normalizeEventName, normalizePalletName, normalizeStorageName } from "./services/runtimeMetadataService";
const networkLoader = ({ params }: LoaderFunctionArgs) => {
@@ -35,252 +37,257 @@ const networkLoader = ({ params }: LoaderFunctionArgs) => {
export const routes: RouteObject[] = [
{
- path: "/",
- element: ,
- },
- {
- element: ,
+ element: ,
children: [
{
- path: "search/:tab?",
- element: ,
- errorElement: ,
+ index: true,
+ element: ,
},
{
- id: "network",
- path: ":network",
- loader: networkLoader,
- errorElement: ,
+ element: ,
children: [
{
- path: ":tab?",
- element: ,
+ path: "search/:tab?",
+ element: ,
+ errorElement: ,
},
{
- path: "block/:id/:tab?",
- element: ,
- loader: (args) => {
- const { params } = args;
- const { id } = params as { id: string };
+ id: "network",
+ path: ":network",
+ loader: networkLoader,
+ errorElement: ,
+ children: [
+ {
+ path: ":tab?",
+ element: ,
+ },
+ {
+ path: "block/:id/:tab?",
+ element: ,
+ loader: (args) => {
+ const { params } = args;
+ const { id } = params as { id: string };
- const simplifiedId = simplifyBlockId(id);
+ const simplifiedId = simplifyBlockId(id);
- if (id !== simplifiedId) {
- const { network } = networkLoader(args);
- return redirect(`/${network.name}/block/${simplifiedId}`);
- }
+ if (id !== simplifiedId) {
+ const { network } = networkLoader(args);
+ return redirect(`/${network.name}/block/${simplifiedId}`);
+ }
- return null;
- }
- },
- {
- path: "extrinsic/:id/:tab?",
- element: ,
- loader: (args) => {
- const { params } = args;
- const { id } = params as { id: string };
+ return null;
+ }
+ },
+ {
+ path: "extrinsic/:id/:tab?",
+ element: ,
+ loader: (args) => {
+ const { params } = args;
+ const { id } = params as { id: string };
- const simplifiedId = simplifyExtrinsicId(id);
+ const simplifiedId = simplifyExtrinsicId(id);
- if (id !== simplifiedId) {
- const { network } = networkLoader(args);
- return redirect(`/${network.name}/extrinsic/${simplifiedId}`);
- }
+ if (id !== simplifiedId) {
+ const { network } = networkLoader(args);
+ return redirect(`/${network.name}/extrinsic/${simplifiedId}`);
+ }
- return null;
- }
- },
- {
- path: "call/:id/:tab?",
- element: ,
- loader: (args) => {
- const { params } = args;
- const { id } = params as { id: string };
+ return null;
+ }
+ },
+ {
+ path: "call/:id/:tab?",
+ element: ,
+ loader: (args) => {
+ const { params } = args;
+ const { id } = params as { id: string };
- const simplifiedId = simplifyCallId(id);
+ const simplifiedId = simplifyCallId(id);
- if (id !== simplifiedId) {
- const { network } = networkLoader(args);
- return redirect(`/${network.name}/call/${simplifiedId}`);
- }
+ if (id !== simplifiedId) {
+ const { network } = networkLoader(args);
+ return redirect(`/${network.name}/call/${simplifiedId}`);
+ }
- return null;
- }
- },
- {
- path: "event/:id",
- element: ,
- loader: (args) => {
- const { params } = args;
- const { id } = params as { id: string };
+ return null;
+ }
+ },
+ {
+ path: "event/:id",
+ element: ,
+ loader: (args) => {
+ const { params } = args;
+ const { id } = params as { id: string };
- const simplifiedId = simplifyEventId(id);
+ const simplifiedId = simplifyEventId(id);
- if (id !== simplifiedId) {
- const { network } = networkLoader(args);
- return redirect(`/${network.name}/event/${simplifiedId}`);
- }
+ if (id !== simplifiedId) {
+ const { network } = networkLoader(args);
+ return redirect(`/${network.name}/event/${simplifiedId}`);
+ }
- return null;
- }
- },
- {
- path: "account/:address/:tab?",
- element: ,
- loader: (args) => {
- const { params } = args;
- const { address } = params as { address: string };
+ return null;
+ }
+ },
+ {
+ path: "account/:address/:tab?",
+ element: ,
+ loader: (args) => {
+ const { params } = args;
+ const { address } = params as { address: string };
- const { network } = networkLoader(args);
+ const { network } = networkLoader(args);
- const encodedAddress = encodeAddress(address, network.prefix);
- if (address !== encodedAddress) {
- return redirect(`/${network.name}/account/${encodedAddress}`);
- }
+ const encodedAddress = encodeAddress(address, network.prefix);
+ if (address !== encodedAddress) {
+ return redirect(`/${network.name}/account/${encodedAddress}`);
+ }
- return null;
- }
- },
- {
- path: "latest-extrinsics",
- element: ,
- },
- {
- path: "runtime",
- children: [
+ return null;
+ }
+ },
{
- index: true,
- element: ,
+ path: "latest-extrinsics",
+ element: ,
},
{
- path: ":specVersion",
+ path: "runtime",
children: [
{
index: true,
- element: ,
+ element: ,
},
{
- id: "runtime-pallet",
- path: ":palletName",
- loader: async (args) => {
- const { params } = args;
- const { specVersion, palletName } = params as { specVersion: string, palletName: string };
-
- const { network } = networkLoader(args);
-
- console.log("pallet loader", specVersion, palletName);
-
- return {
- palletName: await normalizePalletName(network, palletName, specVersion)
- };
- },
+ path: ":specVersion",
children: [
{
- path: ":tab?",
- element: ,
+ index: true,
+ element: ,
},
{
- id: "runtime-call",
- path: "calls/:callName",
+ id: "runtime-pallet",
+ path: ":palletName",
loader: async (args) => {
const { params } = args;
- const { specVersion, palletName, callName } = params as { specVersion: string, palletName: string, callName: string };
+ const { specVersion, palletName } = params as { specVersion: string, palletName: string };
const { network } = networkLoader(args);
- const callFullName = `${palletName}.${callName}`;
- const normalizedCallFullName = await normalizeCallName(network, callFullName, specVersion);
+ console.log("pallet loader", specVersion, palletName);
return {
- callName: normalizedCallFullName.split(".")[1] as string
+ palletName: await normalizePalletName(network, palletName, specVersion)
};
},
- element:
+ children: [
+ {
+ path: ":tab?",
+ element: ,
+ },
+ {
+ id: "runtime-call",
+ path: "calls/:callName",
+ loader: async (args) => {
+ const { params } = args;
+ const { specVersion, palletName, callName } = params as { specVersion: string, palletName: string, callName: string };
+
+ const { network } = networkLoader(args);
+
+ const callFullName = `${palletName}.${callName}`;
+ const normalizedCallFullName = await normalizeCallName(network, callFullName, specVersion);
+
+ return {
+ callName: normalizedCallFullName.split(".")[1] as string
+ };
+ },
+ element:
+ },
+ {
+ id: "runtime-event",
+ path: "events/:eventName",
+ loader: async (args) => {
+ const { params } = args;
+ const { specVersion, palletName, eventName } = params as { specVersion: string, palletName: string, eventName: string };
+
+ const { network } = networkLoader(args);
+
+ const eventFullName = `${palletName}.${eventName}`;
+ const normalizedEventFullName = await normalizeEventName(network, eventFullName, specVersion);
+
+ return {
+ eventName: normalizedEventFullName.split(".")[1] as string
+ };
+ },
+ element:
+ },
+ {
+ id: "runtime-constant",
+ path: "constants/:constantName",
+ loader: async (args) => {
+ const { params } = args;
+ const { specVersion, palletName, constantName } = params as { specVersion: string, palletName: string, constantName: string };
+
+ const { network } = networkLoader(args);
+
+ const constantFullName = `${palletName}.${constantName}`;
+ const normalizedConstantFullName = await normalizeConstantName(network, constantFullName, specVersion);
+
+ return {
+ constantName: normalizedConstantFullName.split(".")[1] as string
+ };
+ },
+ element:
+ },
+ {
+ id: "runtime-storage",
+ path: "storages/:storageName",
+ loader: async (args) => {
+ const { params } = args;
+ const { specVersion, palletName, storageName } = params as { specVersion: string, palletName: string, storageName: string };
+
+ const { network } = networkLoader(args);
+
+ const storageFullName = `${palletName}.${storageName}`;
+ const normalizedStorageFullName = await normalizeStorageName(network, storageFullName, specVersion);
+
+ return {
+ storageName: normalizedStorageFullName.split(".")[1] as string
+ };
+ },
+ element:
+ },
+ {
+ id: "runtime-error",
+ path: "errors/:errorName",
+ loader: async (args) => {
+ const { params } = args;
+ const { specVersion, palletName, errorName } = params as { specVersion: string, palletName: string, errorName: string };
+
+ const { network } = networkLoader(args);
+
+ const errorFullName = `${palletName}.${errorName}`;
+ const normalizedErrorFullName = await normalizeErrorName(network, errorFullName, specVersion);
+
+ return {
+ errorName: normalizedErrorFullName.split(".")[1] as string
+ };
+ },
+ element:
+ }
+ ]
},
- {
- id: "runtime-event",
- path: "events/:eventName",
- loader: async (args) => {
- const { params } = args;
- const { specVersion, palletName, eventName } = params as { specVersion: string, palletName: string, eventName: string };
-
- const { network } = networkLoader(args);
-
- const eventFullName = `${palletName}.${eventName}`;
- const normalizedEventFullName = await normalizeEventName(network, eventFullName, specVersion);
-
- return {
- eventName: normalizedEventFullName.split(".")[1] as string
- };
- },
- element:
- },
- {
- id: "runtime-constant",
- path: "constants/:constantName",
- loader: async (args) => {
- const { params } = args;
- const { specVersion, palletName, constantName } = params as { specVersion: string, palletName: string, constantName: string };
-
- const { network } = networkLoader(args);
-
- const constantFullName = `${palletName}.${constantName}`;
- const normalizedConstantFullName = await normalizeConstantName(network, constantFullName, specVersion);
-
- return {
- constantName: normalizedConstantFullName.split(".")[1] as string
- };
- },
- element:
- },
- {
- id: "runtime-storage",
- path: "storages/:storageName",
- loader: async (args) => {
- const { params } = args;
- const { specVersion, palletName, storageName } = params as { specVersion: string, palletName: string, storageName: string };
-
- const { network } = networkLoader(args);
-
- const storageFullName = `${palletName}.${storageName}`;
- const normalizedStorageFullName = await normalizeStorageName(network, storageFullName, specVersion);
-
- return {
- storageName: normalizedStorageFullName.split(".")[1] as string
- };
- },
- element:
- },
- {
- id: "runtime-error",
- path: "errors/:errorName",
- loader: async (args) => {
- const { params } = args;
- const { specVersion, palletName, errorName } = params as { specVersion: string, palletName: string, errorName: string };
-
- const { network } = networkLoader(args);
-
- const errorFullName = `${palletName}.${errorName}`;
- const normalizedErrorFullName = await normalizeErrorName(network, errorFullName, specVersion);
-
- return {
- errorName: normalizedErrorFullName.split(".")[1] as string
- };
- },
- element:
- }
]
- },
+ }
]
- }
- ]
- },
- {
- path: "*",
- element: ,
+ },
+ {
+ path: "*",
+ element: ,
+ },
+ ],
},
- ],
- },
+ ]
+ }
]
}
];
diff --git a/src/screens/home.tsx b/src/screens/home.tsx
index 6738635b..4f0c34db 100644
--- a/src/screens/home.tsx
+++ b/src/screens/home.tsx
@@ -8,6 +8,7 @@ import SearchInput from "../components/SearchInput";
import { Footer } from "../components/Footer";
import { Card } from "../components/Card";
import { ButtonLink } from "../components/ButtonLink";
+
import { useNetworkGroups } from "../hooks/useNetworkGroups";
const containerStyle = (theme: Theme) => css`
diff --git a/src/services/runtimeMetadataService.ts b/src/services/runtimeMetadataService.ts
index 56b5265f..a617598b 100644
--- a/src/services/runtimeMetadataService.ts
+++ b/src/services/runtimeMetadataService.ts
@@ -159,7 +159,7 @@ export async function normalizeErrorName(network: Network, name: string, specVer
/*** PRIVATE ***/
-async function loadRuntimeMetadata(network: string, specVersion: string) {
+export async function loadRuntimeMetadata(network: string, specVersion: string) {
await self.navigator.locks.request(`runtime-metadata/${network}/${specVersion}`, async () => {
const spec = await runtimeMetadataRepository.specs.get([network, specVersion]);