mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-06-13 19:51:07 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# @pezkuwi/app-storage
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-apps/issues",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-apps/tree/master/packages/page-storage#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/app-storage",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"directory": "packages/page-storage",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-apps.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.168.2-4-x",
|
||||
"dependencies": {
|
||||
"@pezkuwi/react-components": "^0.168.2-4-x",
|
||||
"@pezkuwi/react-params": "^0.168.2-4-x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-dom": "*",
|
||||
"react-is": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { QueryTypes } from './types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Query from './Query.js';
|
||||
|
||||
interface Props {
|
||||
onRemove: (id: number) => void;
|
||||
value?: QueryTypes[];
|
||||
}
|
||||
|
||||
function Queries ({ onRemove, value }: Props): React.ReactElement<Props> | null {
|
||||
if (!value?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className='storage--Queries'>
|
||||
{value.map((query): React.ReactNode =>
|
||||
<Query
|
||||
key={query.id}
|
||||
onRemove={onRemove}
|
||||
value={query}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Queries);
|
||||
@@ -0,0 +1,227 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { QueryableStorageEntry } from '@pezkuwi/api/types';
|
||||
import type { ComponentRenderer, DefaultProps, RenderFn } from '@pezkuwi/react-api/hoc/types';
|
||||
import type { ConstValue } from '@pezkuwi/react-components/InputConsts/types';
|
||||
import type { Option, Raw } from '@pezkuwi/types';
|
||||
import type { Registry } from '@pezkuwi/types/types';
|
||||
import type { QueryTypes, StorageModuleQuery } from './types.js';
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { withCallDiv } from '@pezkuwi/react-api/hoc';
|
||||
import { Button, Labelled, styled } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import valueToText from '@pezkuwi/react-params/valueToText';
|
||||
import { getSiName } from '@pezkuwi/types/metadata/util';
|
||||
import { unwrapStorageType } from '@pezkuwi/types/util';
|
||||
import { compactStripLength, isU8a, u8aToHex, u8aToString } from '@pezkuwi/util';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
onRemove: (id: number) => void;
|
||||
value: QueryTypes;
|
||||
}
|
||||
|
||||
interface CacheInstance {
|
||||
Component: React.ComponentType<any>;
|
||||
render: RenderFn;
|
||||
refresh: (swallowErrors: boolean) => React.ComponentType<any>;
|
||||
}
|
||||
|
||||
const cache: CacheInstance[] = [];
|
||||
|
||||
function keyToName (isConst: boolean, _key: Uint8Array | QueryableStorageEntry<'promise'> | ConstValue): string {
|
||||
if (isConst) {
|
||||
const key = _key as ConstValue;
|
||||
|
||||
return `const ${key.section}.${key.method}`;
|
||||
}
|
||||
|
||||
const key = _key as Uint8Array | QueryableStorageEntry<'promise'>;
|
||||
|
||||
if (isU8a(key)) {
|
||||
const [, u8a] = compactStripLength(key);
|
||||
|
||||
// If the string starts with `:`, handle it as a pure string
|
||||
return u8a[0] === 0x3a
|
||||
? u8aToString(u8a)
|
||||
: u8aToHex(u8a);
|
||||
}
|
||||
|
||||
return `${key.creator.section}.${key.creator.method}`;
|
||||
}
|
||||
|
||||
function constTypeToString (registry: Registry, { meta }: ConstValue): string {
|
||||
return getSiName(registry.lookup, meta.type);
|
||||
}
|
||||
|
||||
function queryTypeToString (registry: Registry, { creator: { meta: { modifier, type } } }: QueryableStorageEntry<'promise'>): string {
|
||||
const _type = unwrapStorageType(registry, type);
|
||||
|
||||
return modifier.isOptional
|
||||
? `Option<${_type}>`
|
||||
: _type;
|
||||
}
|
||||
|
||||
function createComponent (type: string, Component: React.ComponentType<any>, defaultProps: DefaultProps, renderHelper: ComponentRenderer): { Component: React.ComponentType<any>; render: (createComponent: RenderFn) => React.ComponentType<any>; refresh: (swallowErrors: boolean) => React.ComponentType<any> } {
|
||||
return {
|
||||
Component,
|
||||
// In order to modify the parameters which are used to render the default component, we can use this method
|
||||
refresh: (): React.ComponentType<any> =>
|
||||
renderHelper(
|
||||
(value: unknown) => <pre>{valueToText(type, value as null)}</pre>,
|
||||
defaultProps
|
||||
),
|
||||
// In order to replace the default component during runtime we can provide a RenderFn to create a new 'plugged' component
|
||||
render: (createComponent: RenderFn): React.ComponentType<any> =>
|
||||
renderHelper(createComponent, defaultProps)
|
||||
};
|
||||
}
|
||||
|
||||
function getCachedComponent (registry: Registry, query: QueryTypes): CacheInstance {
|
||||
const { blockHash, id, isConst, key, params = [] } = query as StorageModuleQuery;
|
||||
|
||||
if (!cache[id]) {
|
||||
let renderHelper;
|
||||
let type: string;
|
||||
|
||||
if (isConst) {
|
||||
const { method, section } = key as unknown as ConstValue;
|
||||
|
||||
renderHelper = withCallDiv(`consts.${section}.${method}`, { withIndicator: true });
|
||||
type = constTypeToString(registry, key as unknown as ConstValue);
|
||||
} else {
|
||||
if (isU8a(key)) {
|
||||
// subscribe to the raw key here
|
||||
renderHelper = withCallDiv('rpc.state.subscribeStorage', {
|
||||
paramName: 'params',
|
||||
paramValid: true,
|
||||
params: [[key]],
|
||||
transform: ([data]: Option<Raw>[]): Option<Raw> => data,
|
||||
withIndicator: true
|
||||
});
|
||||
} else {
|
||||
const values: unknown[] = params.map(({ value }) => value);
|
||||
const { creator: { meta: { type } } } = key;
|
||||
const allCount = type.isPlain
|
||||
? 0
|
||||
: type.asMap.hashers.length;
|
||||
const isEntries = values.length !== allCount;
|
||||
|
||||
renderHelper = withCallDiv('subscribe', {
|
||||
paramName: 'params',
|
||||
paramValid: true,
|
||||
params: isEntries
|
||||
? [key.entries, ...values]
|
||||
: blockHash
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
? [key.at, blockHash, ...values]
|
||||
: [key, ...values],
|
||||
withIndicator: true
|
||||
});
|
||||
}
|
||||
|
||||
type = key.creator?.meta
|
||||
? queryTypeToString(registry, key)
|
||||
: 'Raw';
|
||||
}
|
||||
|
||||
const defaultProps = { className: 'ui--output' };
|
||||
const Component = renderHelper(
|
||||
// By default we render a simple div node component with the query results in it
|
||||
(value: unknown) => <pre>{valueToText(type, value as null)}</pre>,
|
||||
defaultProps
|
||||
);
|
||||
|
||||
cache[query.id] = createComponent(type, Component, defaultProps, renderHelper);
|
||||
}
|
||||
|
||||
return cache[id];
|
||||
}
|
||||
|
||||
function Query ({ className = '', onRemove, value }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const [{ Component }, callName, callType] = useMemo(
|
||||
() => [
|
||||
getCachedComponent(api.registry, value),
|
||||
keyToName(value.isConst, value.key),
|
||||
value.isConst
|
||||
? constTypeToString(api.registry, value.key as unknown as ConstValue)
|
||||
: isU8a(value.key)
|
||||
? 'Raw'
|
||||
: queryTypeToString(api.registry, value.key as QueryableStorageEntry<'promise'>)
|
||||
],
|
||||
[api, value]
|
||||
);
|
||||
|
||||
const _onRemove = useCallback(
|
||||
(): void => {
|
||||
delete cache[value.id];
|
||||
|
||||
onRemove(value.id);
|
||||
},
|
||||
[onRemove, value]
|
||||
);
|
||||
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledDiv className={`${className} storage--Query storage--actionrow`}>
|
||||
<div className='storage--actionrow-value'>
|
||||
<Labelled
|
||||
label={
|
||||
<div className='storage--actionrow-label'>
|
||||
{callName}: {callType}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Component />
|
||||
</Labelled>
|
||||
</div>
|
||||
<div className='storage--actionrow-buttons'>
|
||||
<Button
|
||||
icon='times'
|
||||
key='close'
|
||||
onClick={_onRemove}
|
||||
/>
|
||||
</div>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
label {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.ui.disabled.dropdown.selection {
|
||||
color: #aaa;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ui--IdentityIcon {
|
||||
margin: -10px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0.5;
|
||||
|
||||
.ui--Param-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.storage--actionrow-buttons {
|
||||
margin-top: -0.25rem; /* offset parent spacing for buttons */
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Query);
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ConstValue } from '@pezkuwi/react-components/InputConsts/types';
|
||||
import type { ConstantCodec } from '@pezkuwi/types/metadata/decorate/types';
|
||||
import type { ComponentProps as Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, InputConsts } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
function Consts ({ onAdd }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [defaultValue] = useState<ConstValue>((): ConstValue => {
|
||||
const section = Object.keys(api.consts)[0];
|
||||
const method = Object.keys(api.consts[section])[0];
|
||||
|
||||
return {
|
||||
meta: (api.consts[section][method] as ConstantCodec).meta,
|
||||
method,
|
||||
section
|
||||
};
|
||||
});
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
const _onAdd = useCallback(
|
||||
() => onAdd({ isConst: true, key: value }),
|
||||
[onAdd, value]
|
||||
);
|
||||
|
||||
return (
|
||||
<section className='storage--actionrow'>
|
||||
<div className='storage--actionrow-value'>
|
||||
<InputConsts
|
||||
defaultValue={defaultValue}
|
||||
label={t('selected constant query')}
|
||||
onChange={setValue}
|
||||
/>
|
||||
</div>
|
||||
<div className='storage--actionrow-buttons'>
|
||||
<Button
|
||||
icon='plus'
|
||||
onClick={_onAdd}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Consts);
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { QueryableStorageEntry } from '@pezkuwi/api/types';
|
||||
import type { RawParams, TypeDefExt } from '@pezkuwi/react-params/types';
|
||||
import type { StorageEntryTypeLatest } from '@pezkuwi/types/interfaces';
|
||||
import type { Inspect, Registry } from '@pezkuwi/types/types';
|
||||
import type { ComponentProps as Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { Button, Columar, Input, InputStorage, Inspect as DecodeInspect, Output, styled } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import Params from '@pezkuwi/react-params';
|
||||
import { getTypeDef } from '@pezkuwi/types';
|
||||
import { getSiName } from '@pezkuwi/types/metadata/util';
|
||||
import { TypeDefInfo } from '@pezkuwi/types/types';
|
||||
import { compactStripLength, isHex, isNull, isUndefined, u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
type ParamsType = { name?: string, type: TypeDefExt }[];
|
||||
|
||||
interface KeyState {
|
||||
defaultValues: RawParams | undefined | null;
|
||||
isIterable: boolean;
|
||||
key: QueryableStorageEntry<'promise'>;
|
||||
params: ParamsType;
|
||||
}
|
||||
|
||||
interface ValState {
|
||||
isValid: boolean;
|
||||
values: RawParams;
|
||||
}
|
||||
|
||||
interface BlockHash {
|
||||
blockHash: string | null;
|
||||
textHash: string;
|
||||
}
|
||||
|
||||
function areParamsValid ({ creator: { meta: { type } } }: QueryableStorageEntry<'promise'>, values: RawParams): boolean {
|
||||
return values.reduce((isValid: boolean, value) =>
|
||||
isValid &&
|
||||
!isUndefined(value) &&
|
||||
!isUndefined(value.value) &&
|
||||
value.isValid,
|
||||
(values.length === (type.isPlain ? 0 : type.asMap.hashers.length)));
|
||||
}
|
||||
|
||||
function expandParams (registry: Registry, st: StorageEntryTypeLatest, isIterable: boolean): ParamsType {
|
||||
let types: string[] = [];
|
||||
|
||||
if (st.isMap) {
|
||||
const { hashers, key } = st.asMap;
|
||||
|
||||
types = hashers.length === 1
|
||||
? [getSiName(registry.lookup, key)]
|
||||
: registry.lookup.getSiType(key).def.asTuple.map((k) => getSiName(registry.lookup, k));
|
||||
}
|
||||
|
||||
return types.map((str, index) => {
|
||||
let name: string | undefined;
|
||||
let type: TypeDefExt;
|
||||
|
||||
if (isIterable && index === (types.length - 1)) {
|
||||
// name = 'entryKey';
|
||||
type = getTypeDef(`Option<${str}>`);
|
||||
type.withOptionActive = true;
|
||||
} else {
|
||||
type = getTypeDef(str);
|
||||
}
|
||||
|
||||
return { name, type };
|
||||
});
|
||||
}
|
||||
|
||||
function checkIterable (registry: Registry, type: StorageEntryTypeLatest): boolean {
|
||||
// in the case of Option<type> keys, we don't allow map iteration, in this case
|
||||
// we would have option for the iterable and then option for the key value
|
||||
if (type.isPlain) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { hashers, key } = type.asMap;
|
||||
|
||||
if (hashers.length === 1) {
|
||||
return registry.lookup.getTypeDef(key).info !== TypeDefInfo.Option;
|
||||
}
|
||||
|
||||
const keys = registry.lookup.getSiType(key).def.asTuple;
|
||||
|
||||
return registry.lookup.getTypeDef(keys[keys.length - 1]).info !== TypeDefInfo.Option;
|
||||
}
|
||||
|
||||
function expandKey (api: ApiPromise, key: QueryableStorageEntry<'promise'>): KeyState {
|
||||
const { creator: { meta: { type }, section } } = key;
|
||||
const isIterable = checkIterable(api.registry, type);
|
||||
|
||||
return {
|
||||
defaultValues: section === 'session' && type.isMap && api.consts.session?.dedupKeyPrefix
|
||||
? [{ isValid: true, value: api.consts.session.dedupKeyPrefix.toHex() }]
|
||||
: null,
|
||||
isIterable,
|
||||
key,
|
||||
params: expandParams(api.registry, type, isIterable)
|
||||
};
|
||||
}
|
||||
|
||||
function extractParams (isIterable: boolean, values: RawParams): [RawParams, boolean] {
|
||||
const params = values.filter(({ value }, index) => !isIterable || (index !== values.length - 1) || !isNull(value));
|
||||
|
||||
return [params, params.length === values.length];
|
||||
}
|
||||
|
||||
function Modules ({ className = '', onAdd }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [{ defaultValues, isIterable, key, params }, setKey] = useState<KeyState>(() => ({ defaultValues: undefined, isHeadKey: true, isIterable: false, key: api.query.timestamp?.now || api.query.system.events, params: [] }));
|
||||
const [{ isValid, values }, setValues] = useState<ValState>(() => ({ isValid: true, values: [] }));
|
||||
const [{ blockHash, textHash }, setBlockHash] = useState<BlockHash>({ blockHash: null, textHash: '' });
|
||||
|
||||
const startValue = useMemo(
|
||||
() => (
|
||||
api.query.timestamp?.now ||
|
||||
api.query.system?.events ||
|
||||
api.query.bizinikiwi.changesTrieConfig
|
||||
),
|
||||
[api]
|
||||
);
|
||||
|
||||
const [isPartialKey, hexKey, inspect] = useMemo(
|
||||
(): [boolean, string, Inspect | null] => {
|
||||
if (isValid) {
|
||||
try {
|
||||
const [params] = extractParams(isIterable, values);
|
||||
const args = params.map(({ value }) => value);
|
||||
const isPartialKey = args.length !== (
|
||||
key.creator.meta.type.isPlain
|
||||
? 0
|
||||
: key.creator.meta.type.asMap.hashers.length
|
||||
);
|
||||
const hexKey = isPartialKey && key.creator.iterKey
|
||||
? key.creator.iterKey(...args).toHex()
|
||||
: u8aToHex(compactStripLength(key.creator(...args))[1]);
|
||||
const inspect = isPartialKey
|
||||
? null
|
||||
: key.creator.inspect(...args);
|
||||
|
||||
return [isPartialKey, hexKey, inspect];
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return [false, '0x', null];
|
||||
},
|
||||
[isIterable, isValid, key, values]
|
||||
);
|
||||
|
||||
const _onAdd = useCallback(
|
||||
(): void => {
|
||||
const [params, isAtEnabled] = extractParams(isIterable, values);
|
||||
|
||||
isValid && onAdd({
|
||||
blockHash: isAtEnabled
|
||||
? blockHash
|
||||
: null,
|
||||
isConst: false,
|
||||
key,
|
||||
params
|
||||
});
|
||||
},
|
||||
[blockHash, isIterable, isValid, key, onAdd, values]
|
||||
);
|
||||
|
||||
const _onChangeAt = useCallback(
|
||||
(textHash: string) => setBlockHash({
|
||||
blockHash: isHex(textHash, 256)
|
||||
? textHash
|
||||
: null,
|
||||
textHash
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onChangeValues = useCallback(
|
||||
(values: RawParams) => setValues({
|
||||
isValid: areParamsValid(key, values),
|
||||
values
|
||||
}),
|
||||
[key]
|
||||
);
|
||||
|
||||
const _onChangeKey = useCallback(
|
||||
(key: QueryableStorageEntry<'promise'>): void => {
|
||||
setKey(expandKey(api, key));
|
||||
_onChangeValues([]);
|
||||
},
|
||||
[_onChangeValues, api]
|
||||
);
|
||||
|
||||
const isAtAllowed = useMemo(
|
||||
() => isValid
|
||||
? extractParams(isIterable, values)[1]
|
||||
: false,
|
||||
[isIterable, isValid, values]
|
||||
);
|
||||
|
||||
const { creator: { method, section } } = key;
|
||||
|
||||
return (
|
||||
<StyledSection className={`${className} storage--actionrow`}>
|
||||
<div className='storage--actionrow-value'>
|
||||
<InputStorage
|
||||
defaultValue={startValue}
|
||||
label={t('selected state query')}
|
||||
onChange={_onChangeKey}
|
||||
/>
|
||||
<Params
|
||||
key={`${section}.${method}:params` /* force re-render on change */}
|
||||
onChange={_onChangeValues}
|
||||
onEnter={_onAdd}
|
||||
params={params}
|
||||
values={defaultValues}
|
||||
/>
|
||||
<Input
|
||||
isDisabled={!isValid || !isAtAllowed}
|
||||
isError={!!textHash && !blockHash}
|
||||
label={t('blockhash to query at')}
|
||||
onChange={_onChangeAt}
|
||||
placeholder={t('0x...')}
|
||||
/>
|
||||
<Columar
|
||||
className='keyColumar'
|
||||
isPadded={false}
|
||||
>
|
||||
<Columar.Column>
|
||||
<Output
|
||||
isDisabled
|
||||
label={isPartialKey
|
||||
? t('encoded partial key')
|
||||
: t('encoded storage key')
|
||||
}
|
||||
value={hexKey}
|
||||
withCopy
|
||||
/>
|
||||
</Columar.Column>
|
||||
<Columar.Column>
|
||||
<DecodeInspect
|
||||
inspect={inspect}
|
||||
label={t('encoded key details')}
|
||||
/>
|
||||
</Columar.Column>
|
||||
</Columar>
|
||||
</div>
|
||||
<div className='storage--actionrow-buttons'>
|
||||
<Button
|
||||
icon='plus'
|
||||
isDisabled={!isValid}
|
||||
onClick={_onAdd}
|
||||
/>
|
||||
</div>
|
||||
</StyledSection>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledSection = styled.section`
|
||||
.ui--Column:last-child .ui--Labelled {
|
||||
padding-left: 0.5rem;
|
||||
|
||||
label {
|
||||
left: 2.05rem; /* 3.55 - 1.5 (diff from padding above) */
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Modules);
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ComponentProps as Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, Input } from '@pezkuwi/react-components';
|
||||
import { compactAddLength, u8aToU8a } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
function Raw ({ onAdd }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [{ isValid, key }, setValue] = useState<{ isValid: boolean; key: Uint8Array }>(() => ({ isValid: false, key: new Uint8Array([]) }));
|
||||
|
||||
const _onAdd = useCallback(
|
||||
(): void => {
|
||||
isValid && onAdd({ isConst: false, key });
|
||||
},
|
||||
[isValid, key, onAdd]
|
||||
);
|
||||
|
||||
const _onChangeKey = useCallback(
|
||||
(key: string): void => {
|
||||
const u8a = u8aToU8a(key);
|
||||
|
||||
setValue({
|
||||
isValid: u8a.length !== 0,
|
||||
key: compactAddLength(u8a)
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<section className='storage--actionrow'>
|
||||
<div className='storage--actionrow-value'>
|
||||
<Input
|
||||
autoFocus
|
||||
label={t('hex-encoded storage key')}
|
||||
onChange={_onChangeKey}
|
||||
onEnter={_onAdd}
|
||||
/>
|
||||
</div>
|
||||
<div className='storage--actionrow-buttons'>
|
||||
<Button
|
||||
icon='plus'
|
||||
isDisabled={!isValid}
|
||||
onClick={_onAdd}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Raw);
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ParitalQueryTypes, QueryTypes } from '../types.js';
|
||||
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { Route, Routes } from 'react-router';
|
||||
|
||||
import { Tabs } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Consts from './Consts.js';
|
||||
import Modules from './Modules.js';
|
||||
import Raw from './Raw.js';
|
||||
|
||||
interface Props {
|
||||
basePath: string;
|
||||
onAdd: (query: QueryTypes) => void;
|
||||
}
|
||||
|
||||
let id = -1;
|
||||
|
||||
function Selection ({ basePath, onAdd }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const itemsRef = useRef([
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'modules',
|
||||
text: t('Storage')
|
||||
},
|
||||
{
|
||||
name: 'constants',
|
||||
text: t('Constants')
|
||||
},
|
||||
{
|
||||
name: 'raw',
|
||||
text: t('Raw storage')
|
||||
}
|
||||
]);
|
||||
|
||||
const _onAdd = useCallback(
|
||||
(query: ParitalQueryTypes) => onAdd({ ...query, id: ++id }),
|
||||
[onAdd]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={itemsRef.current}
|
||||
/>
|
||||
<Routes>
|
||||
<Route path={basePath}>
|
||||
<Route
|
||||
element={
|
||||
<Consts onAdd={_onAdd} />
|
||||
}
|
||||
path='constants'
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Raw onAdd={_onAdd} />
|
||||
}
|
||||
path='raw'
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Modules onAdd={_onAdd} />
|
||||
}
|
||||
index
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Selection);
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AppProps as Props } from '@pezkuwi/react-components/types';
|
||||
import type { QueryTypes } from './types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { styled } from '@pezkuwi/react-components';
|
||||
|
||||
import Selection from './Selection/index.js';
|
||||
import Queries from './Queries.js';
|
||||
|
||||
function StorageApp ({ basePath, className = '' }: Props): React.ReactElement<Props> {
|
||||
const [queue, setQueue] = useState<QueryTypes[]>([]);
|
||||
|
||||
const _onAdd = useCallback(
|
||||
(query: QueryTypes) => setQueue((queue: QueryTypes[]) => [query, ...queue]),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onRemove = useCallback(
|
||||
(id: number) => setQueue((queue: QueryTypes[]) => queue.filter((item) => item.id !== id)),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledMain className={`${className} storage--App`}>
|
||||
<Selection
|
||||
basePath={basePath}
|
||||
onAdd={_onAdd}
|
||||
/>
|
||||
<Queries
|
||||
onRemove={_onRemove}
|
||||
value={queue}
|
||||
/>
|
||||
</StyledMain>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledMain = styled.main`
|
||||
.storage--actionrow {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
|
||||
.ui--Button {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
&.head {
|
||||
flex: 1 1 100%;
|
||||
margin: 0 auto;
|
||||
max-width: 620px;
|
||||
}
|
||||
}
|
||||
|
||||
.storage--actionrow-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.ui--output {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.storage--actionrow-buttons {
|
||||
flex: 0;
|
||||
padding: 0.5rem 0.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(StorageApp);
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useTranslation as useTranslationBase } from 'react-i18next';
|
||||
|
||||
export function useTranslation (): { t: (key: string, options?: { replace: Record<string, unknown> }) => string } {
|
||||
return useTranslationBase('app-storage');
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-storage authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { QueryableStorageEntry } from '@pezkuwi/api/types';
|
||||
import type { ConstValue } from '@pezkuwi/react-components/InputConsts/types';
|
||||
import type { RawParams } from '@pezkuwi/react-params/types';
|
||||
|
||||
interface Base {
|
||||
isConst: boolean;
|
||||
}
|
||||
|
||||
interface IdQuery extends Base {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface PartialModuleQuery extends Base {
|
||||
blockHash: string | null;
|
||||
key: QueryableStorageEntry<'promise'>;
|
||||
params: RawParams;
|
||||
}
|
||||
|
||||
export type StorageModuleQuery = PartialModuleQuery & IdQuery;
|
||||
|
||||
export interface PartialRawQuery extends Base {
|
||||
key: Uint8Array;
|
||||
}
|
||||
|
||||
export type StorageRawQuery = PartialRawQuery & IdQuery;
|
||||
|
||||
export interface PartialConstQuery extends Base {
|
||||
key: ConstValue;
|
||||
}
|
||||
|
||||
export type ConstQuery = PartialConstQuery & IdQuery;
|
||||
|
||||
export type QueryTypes = StorageModuleQuery | StorageRawQuery | ConstQuery;
|
||||
|
||||
export type ParitalQueryTypes = PartialModuleQuery | PartialRawQuery | PartialConstQuery;
|
||||
|
||||
export interface ComponentProps {
|
||||
className?: string;
|
||||
onAdd: (query: ParitalQueryTypes) => void;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../react-components/tsconfig.build.json" },
|
||||
{ "path": "../react-params/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user