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:
2026-01-07 13:05:27 +03:00
commit d21bfb1320
5867 changed files with 329019 additions and 0 deletions
View File
View File
+1
View File
@@ -0,0 +1 @@
# @pezkuwi/app-storage
+27
View File
@@ -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": "*"
}
}
+33
View File
@@ -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);
+227
View File
@@ -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);
+72
View File
@@ -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);
+8
View File
@@ -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');
}
+43
View File
@@ -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;
}
+12
View File
@@ -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" }
]
}