Skip to content

Commit

Permalink
search autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
uiii committed Dec 6, 2023
1 parent a09199b commit 7bd76bb
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 40 deletions.
95 changes: 82 additions & 13 deletions src/components/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/** @jsxImportSource @emotion/react */
import { FormHTMLAttributes, useCallback, useEffect, useState } from "react";
import { FormHTMLAttributes, useCallback, useEffect, useMemo, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { Button, FormGroup, TextField } from "@mui/material";
import { Autocomplete, Button, FormGroup, TextField, debounce } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import { css, Theme } from "@emotion/react";

import { useAutocompleteSearchQuery } from "../hooks/useAutocompleteSearchQuery";
import { Network } from "../model/network";
import { getNetworks } from "../services/networksService";

Expand All @@ -17,7 +18,7 @@ const formGroupStyle = css`
`;

const networkSelectStyle = (theme: Theme) => css`
flex: 1 0 auto;
flex: 0 0 auto;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
Expand Down Expand Up @@ -53,6 +54,18 @@ const networkSelectStyle = (theme: Theme) => css`
}
`;

const inputStyle = css`
flex: 1 0 auto;
.MuiOutlinedInput-root {
padding: 0 !important;
.MuiAutocomplete-input {
padding: 12px 16px;
}
}
`;

const textFieldStyle = css`
.MuiInputBase-root {
border-radius: 0;
Expand All @@ -70,6 +83,25 @@ const textFieldStyle = css`
}
`;

const autocompleteNameStyle = css`
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 16px;
font-size: 14px;
`;

const autocompleteTypeStyle = css`
margin-left: auto;
flex: 0 0 auto;
font-size: 12px;
opacity: .75;
border: solid 1px gray;
border-radius: 8px;
padding: 0 4px;
background-color: rgba(0, 0, 0, .025);
`;

const buttonStyle = (theme: Theme) => css`
border-radius: 8px;
border-top-left-radius: 0px;
Expand Down Expand Up @@ -99,6 +131,14 @@ const buttonStyle = (theme: Theme) => css`
}
`;

function storeNetworks(networks: Network[]) {
localStorage.setItem("networks", JSON.stringify(networks.map(it => it.name)));
}

function loadNetworks() {
return getNetworks(JSON.parse(localStorage.getItem("networks") || "[]"));
}

export type SearchInputProps = FormHTMLAttributes<HTMLFormElement> & {
persist?: boolean;
defaultNetworks?: Network[];
Expand All @@ -109,13 +149,15 @@ function SearchInput(props: SearchInputProps) {

const [qs] = useSearchParams();

const navigate = useNavigate();

const [networks, setNetworks] = useState<Network[]>(defaultNetworks || getNetworks(qs.getAll("network") || []));
const [query, setQuery] = useState<string>(qs.get("query") || "");
const [autocompleteQuery, _setAutocompleteQuery] = useState<string>(query || "");

const navigate = useNavigate();
const setAutocompleteQuery = useMemo(() => debounce(_setAutocompleteQuery, 250), []);

const storeNetworks = (networks: Network[]) => localStorage.setItem("networks", JSON.stringify(networks.map(it => it.name)));
const loadNetworks = () => getNetworks(JSON.parse(localStorage.getItem("networks") || "[]"));
const autocompleteSuggestions = useAutocompleteSearchQuery(autocompleteQuery, networks);

const handleNetworkSelect = useCallback((networks: Network[], isUserAction: boolean) => {
if (isUserAction && persist) {
Expand All @@ -126,6 +168,11 @@ function SearchInput(props: SearchInputProps) {
setNetworks(networks);
}, [persist]);

const handleQueryChange = useCallback((ev: any, value: string) => {
setQuery(value);
setAutocompleteQuery(value);
}, []);

const handleSubmit = useCallback((ev: any) => {
ev.preventDefault();

Expand Down Expand Up @@ -166,13 +213,35 @@ function SearchInput(props: SearchInputProps) {
value={networks}
multiselect
/>
<TextField
css={textFieldStyle}
fullWidth
id="search"
onChange={(e) => setQuery(e.target.value)}
placeholder="Extrinsic hash / account address / block hash / block height / extrinsic name / event name"
value={query}
<Autocomplete
css={inputStyle}
freeSolo
includeInputInList
autoComplete
disableClearable
options={autocompleteSuggestions.data || []}
filterOptions={it => it}
inputValue={query}
onInputChange={handleQueryChange}
renderOption={(props, option) => (
<li {...props}>
<div css={autocompleteNameStyle}>
{option.label.slice(0, option.highlight[0])}
<strong>{option.label.slice(option.highlight[0], option.highlight[1])}</strong>
{option.label.slice(option.highlight[1])}
</div>
<div css={autocompleteTypeStyle}>{option.type}</div>
</li>
)}
renderInput={(params) =>
<TextField
{...params}
css={textFieldStyle}
fullWidth
id="search"
placeholder="Extrinsic hash / account address / block hash / block height / extrinsic name / event name"
/>
}
/>
<Button
css={buttonStyle}
Expand Down
12 changes: 12 additions & 0 deletions src/hooks/useAutocompleteSearchQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Network } from "../model/network";
import { autocompleteSearchQuery } from "../services/searchService";

import { UseResourceOptions, useResource } from "./useResource";

export function useAutocompleteSearchQuery(
query: string,
networks: Network[],
options?: UseResourceOptions
) {
return useResource(autocompleteSearchQuery, [query, networks], options);
}
6 changes: 6 additions & 0 deletions src/model/runtime-metadata/runtimeMetadataAutocomplete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface RuntimeMetadataAutocomplete {
type: "pallet" | "call" | "event";
name: string;
pallet?: string;
networks: string[];
}
8 changes: 4 additions & 4 deletions src/repositories/runtimeMetadataRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ export class RuntimeMetadataRepository extends Dexie {

this.version(1).stores({
specs: "[network+specVersion]",
pallets: "[network+specVersion+name],[network+specVersion]",
calls: "[network+specVersion+pallet+name],[network+specVersion+pallet]",
events: "[network+specVersion+pallet+name],[network+specVersion+pallet]",
pallets: "[network+specVersion+name],[network+specVersion],name",
calls: "[network+specVersion+pallet+name],[network+specVersion+pallet],name",
events: "[network+specVersion+pallet+name],[network+specVersion+pallet],name",
constants: "[network+specVersion+pallet+name],[network+specVersion+pallet]",
storages: "[network+specVersion+pallet+name],[network+specVersion+pallet]",
errors: "[network+specVersion+pallet+name],[network+specVersion+pallet]"
errors: "[network+specVersion+pallet+name],[network+specVersion+pallet]",
});
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/screens/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ const searchInputStyle = (theme: Theme) => css`
flex: 1 1 auto;
.MuiInputBase-root {
.MuiInputBase-input,
.MuiSelect-select {
&.MuiOutlinedInput-root .MuiAutocomplete-input {
padding: 16px 24px;
}
}
Expand Down
40 changes: 25 additions & 15 deletions src/services/runtimeMetadataService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Table } from "dexie";
import { Collection, Table } from "dexie";

import { Network } from "../model/network";
import { RuntimeMetadataCall } from "../model/runtime-metadata/runtimeMetadataCall";
Expand All @@ -16,57 +16,69 @@ import { RuntimeSpecWorker } from "../workers/runtimeSpecWorker";

export async function getRuntimeMetadataPallets(network: string, specVersion: string, filter?: (it: RuntimeMetadataPallet) => boolean) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.pallets, {network, specVersion}, filter).toArray();
return applyFilter(runtimeMetadataRepository.pallets.where({network, specVersion}), filter).toArray();
}

export async function getRuntimeMetadataPalletsByName(namePrefix: string, filter?: (it: RuntimeMetadataPallet) => boolean) {
return applyFilter(runtimeMetadataRepository.pallets.where("name").startsWithIgnoreCase(namePrefix), filter).toArray();
}

export async function getRuntimeMetadataCalls(network: string, specVersion: string, pallet: string, filter?: (it: RuntimeMetadataCall) => boolean) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.calls, {network, specVersion, pallet}, filter).toArray();
return applyFilter(runtimeMetadataRepository.calls.where({network, specVersion, pallet}), filter).toArray();
}

export async function getRuntimeMetadataCallsByName(namePrefix: string, filter?: (it: RuntimeMetadataCall) => boolean) {
return applyFilter(runtimeMetadataRepository.calls.where("name").startsWithIgnoreCase(namePrefix), filter).toArray();
}

export async function getRuntimeMetadataEvents(network: string, specVersion: string, pallet: string, filter?: (it: RuntimeMetadataEvent) => boolean) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.events, {network, specVersion, pallet}, filter).toArray();
return applyFilter(runtimeMetadataRepository.events.where({network, specVersion, pallet}), filter).toArray();
}

export async function getRuntimeMetadataEventsByName(namePrefix: string, filter?: (it: RuntimeMetadataEvent) => boolean) {
return applyFilter(runtimeMetadataRepository.events.where("name").startsWithIgnoreCase(namePrefix), filter).toArray();
}

export async function getRuntimeMetadataConstants(network: string, specVersion: string, pallet: string, filter?: (it: RuntimeMetadataConstant) => boolean) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.constants, {network, specVersion, pallet}, filter).toArray();
return applyFilter(runtimeMetadataRepository.constants.where({network, specVersion, pallet}), filter).toArray();
}

export async function getRuntimeMetadataStorages(network: string, specVersion: string, pallet: string, filter?: (it: RuntimeMetadataStorage) => boolean) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.storages, {network, specVersion, pallet}, filter).toArray();
return applyFilter(runtimeMetadataRepository.storages.where({network, specVersion, pallet}), filter).toArray();
}

export async function getRuntimeMetadataErrors(network: string, specVersion: string, pallet: string, filter?: (it: RuntimeMetadataError) => boolean) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.errors, {network, specVersion, pallet}, filter).toArray();
return applyFilter(runtimeMetadataRepository.errors.where({network, specVersion, pallet}), filter).toArray();
}

export async function getRuntimeMetadataCall(network: string, specVersion: string, pallet: string, name: string) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.calls, {network, specVersion, pallet, name}).first();
return runtimeMetadataRepository.calls.where({network, specVersion, pallet, name}).first();
}

export async function getRuntimeMetadataEvent(network: string, specVersion: string, pallet: string, name: string) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.events, {network, specVersion, pallet, name}).first();
return runtimeMetadataRepository.events.where({network, specVersion, pallet, name}).first();
}

export async function getRuntimeMetadataConstant(network: string, specVersion: string, pallet: string, name: string) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.constants, {network, specVersion, pallet, name}).first();
return runtimeMetadataRepository.constants.where({network, specVersion, pallet, name}).first();
}

export async function getRuntimeMetadataStorage(network: string, specVersion: string, pallet: string, name: string) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.storages, {network, specVersion, pallet, name}).first();
return runtimeMetadataRepository.storages.where({network, specVersion, pallet, name}).first();
}

export async function getRuntimeMetadataError(network: string, specVersion: string, pallet: string, name: string) {
await loadRuntimeMetadata(network, specVersion);
return queryStore(runtimeMetadataRepository.errors, {network, specVersion, pallet, name}).first();
return runtimeMetadataRepository.errors.where({network, specVersion, pallet, name}).first();
}

export async function normalizePalletName(network: Network, name: string, specVersion: string) {
Expand Down Expand Up @@ -165,9 +177,7 @@ async function loadRuntimeMetadata(network: string, specVersion: string) {
});
}

function queryStore<T>(table: Table<T>, where: Record<string, any>, filter?: (it: T) => boolean) {
let collection = table.where(where);

function applyFilter<T>(collection: Collection<T>, filter?: (it: T) => boolean) {
if (filter) {
collection = collection.filter(filter);
}
Expand Down
Loading

0 comments on commit 7bd76bb

Please sign in to comment.