mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-22 18:17:59 +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,29 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { styled } from '@pezkuwi/react-components';
|
||||
|
||||
interface Props {
|
||||
color?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ChainColorIndicator ({ className, color }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv
|
||||
className={className}
|
||||
color={color}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div(({ color }: Props): string => `
|
||||
background-color: ${color || 'white'} !important;
|
||||
width: 100px;
|
||||
flex: 1;
|
||||
border-radius: 4px;
|
||||
`);
|
||||
|
||||
export default React.memo(ChainColorIndicator);
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { MetadataDef } from '@pezkuwi/extension-inject/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { ChainInfo } from '../types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { knownExtensions } from '@pezkuwi/apps-config';
|
||||
import { externalEmptySVG } from '@pezkuwi/apps-config/ui/logos/external';
|
||||
import { Button, Dropdown, Spinner, styled, Table } from '@pezkuwi/react-components';
|
||||
import { useToggle } from '@pezkuwi/react-hooks';
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import useExtensions from '../useExtensions.js';
|
||||
import iconOption from './iconOption.js';
|
||||
|
||||
interface Props {
|
||||
chainInfo: ChainInfo | null;
|
||||
className?: string;
|
||||
rawMetadata: HexString | null;
|
||||
}
|
||||
|
||||
function Extensions ({ chainInfo, className, rawMetadata }: Props): React.ReactElement<Props> {
|
||||
const isMetadataReady = rawMetadata !== null;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { extensions } = useExtensions();
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [isBusy, toggleBusy] = useToggle(true);
|
||||
|
||||
useEffect((): void => {
|
||||
if (isMetadataReady) {
|
||||
toggleBusy();
|
||||
}
|
||||
}, [isMetadataReady, toggleBusy]);
|
||||
|
||||
const options = useMemo(
|
||||
() => (extensions || []).map(({ extension: { name, version } }, value) =>
|
||||
iconOption(`${name} ${version}`, value, knownExtensions[name]?.ui.logo || externalEmptySVG)
|
||||
),
|
||||
[extensions]
|
||||
);
|
||||
|
||||
const _updateMeta = useCallback(
|
||||
(): void => {
|
||||
if (chainInfo && extensions?.[selectedIndex]) {
|
||||
toggleBusy();
|
||||
|
||||
const rawDef: MetadataDef = objectSpread<MetadataDef>({}, { ...chainInfo, rawMetadata });
|
||||
|
||||
extensions[selectedIndex]
|
||||
.update(rawDef)
|
||||
.catch(() => false)
|
||||
.then(() => toggleBusy())
|
||||
.catch(console.error);
|
||||
}
|
||||
},
|
||||
[chainInfo, extensions, rawMetadata, selectedIndex, toggleBusy]
|
||||
);
|
||||
|
||||
const headerRef = useRef<[React.ReactNode?, string?, number?][]>([
|
||||
[t('Extensions'), 'start']
|
||||
]);
|
||||
|
||||
return (
|
||||
<StyledTable
|
||||
className={className}
|
||||
empty={t('No Upgradable extensions')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{extensions
|
||||
? options.length !== 0 && (
|
||||
<>
|
||||
<tr className='isExpanded isFirst'>
|
||||
<td>
|
||||
<Dropdown
|
||||
label={t('upgradable extensions')}
|
||||
onChange={setSelectedIndex}
|
||||
options={options}
|
||||
value={selectedIndex}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className='isExpanded isLast'>
|
||||
<td>
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='upload'
|
||||
isDisabled={isBusy}
|
||||
label={t('Update metadata')}
|
||||
onClick={_updateMeta}
|
||||
/>
|
||||
</Button.Group>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
)
|
||||
: <Spinner />
|
||||
}
|
||||
</StyledTable>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
table {
|
||||
overflow: visible;
|
||||
z-index: 2;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Extensions);
|
||||
@@ -0,0 +1,308 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BlockNumber, RuntimeVersion } from '@pezkuwi/types/interfaces';
|
||||
import type { NetworkSpecsStruct } from '@pezkuwi/ui-settings/types';
|
||||
import type { ChainInfo, ChainType } from '../types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react';
|
||||
|
||||
import { ChainImg, Input, QrNetworkSpecs, Spinner, styled, Table } from '@pezkuwi/react-components';
|
||||
import { useApi, useCall, useDebounce } from '@pezkuwi/react-hooks';
|
||||
import { formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import ChainColorIndicator from './ChainColorIndicator.js';
|
||||
|
||||
interface Props {
|
||||
chainInfo: ChainInfo | null;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// TODO-MOONBEAM: update NetworkSpecsStruct in @pezkuwi/ui-settings/types
|
||||
interface NetworkSpecsStructWithType extends NetworkSpecsStruct{
|
||||
chainType: ChainType
|
||||
}
|
||||
|
||||
function getRandomColor (): string {
|
||||
const letters = '0123456789ABCDEF';
|
||||
let color = '#';
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
chainType: 'bizinikiwi' as ChainType,
|
||||
color: '#FFFFFF',
|
||||
decimals: 0,
|
||||
genesisHash: '',
|
||||
prefix: 0,
|
||||
title: '',
|
||||
unit: 'UNIT'
|
||||
};
|
||||
|
||||
function NetworkSpecs ({ chainInfo, className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady, systemChain } = useApi();
|
||||
const [qrData, setQrData] = useState<NetworkSpecsStructWithType>(initialState);
|
||||
const debouncedQrData = useDebounce(qrData, 500);
|
||||
const runtimeVersion = useCall<RuntimeVersion>(isApiReady && api.rpc.state.subscribeRuntimeVersion);
|
||||
const blockNumber = useCall<BlockNumber>(isApiReady && api.derive.chain.bestNumber);
|
||||
|
||||
const reducer = (state: NetworkSpecsStructWithType, delta: Partial<NetworkSpecsStructWithType>): NetworkSpecsStructWithType => {
|
||||
const newState = {
|
||||
...state,
|
||||
...delta
|
||||
};
|
||||
|
||||
setQrData(newState);
|
||||
|
||||
return newState;
|
||||
};
|
||||
|
||||
const [networkSpecs, setNetworkSpecs] = useReducer(reducer, initialState);
|
||||
|
||||
useEffect((): void => {
|
||||
chainInfo && setNetworkSpecs({
|
||||
chainType: chainInfo.chainType,
|
||||
color: chainInfo.color || getRandomColor(),
|
||||
decimals: chainInfo.tokenDecimals,
|
||||
genesisHash: chainInfo.genesisHash,
|
||||
prefix: chainInfo.ss58Format,
|
||||
title: systemChain,
|
||||
unit: chainInfo.tokenSymbol
|
||||
});
|
||||
}, [chainInfo, systemChain]);
|
||||
|
||||
const _onChangeColor = useCallback(
|
||||
(color: string): void => setNetworkSpecs({ color }),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onSetRandomColor = useCallback(
|
||||
(event: React.MouseEvent<unknown>): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
setNetworkSpecs({ color: getRandomColor() });
|
||||
},
|
||||
[]
|
||||
);
|
||||
const _checkColorValid = useCallback(
|
||||
(): boolean => /^#[\da-fA-F]{6}|#[\da-fA-F]{3}$/.test(networkSpecs.color),
|
||||
[networkSpecs]
|
||||
);
|
||||
|
||||
const headerRef = useRef<[React.ReactNode?, string?, number?][]>([
|
||||
[t('chain specifications'), 'start', 2]
|
||||
]);
|
||||
|
||||
if (!isApiReady) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledTable
|
||||
className={className}
|
||||
empty={t('No open tips')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<div className='settings--networkSpecs-name'>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Network Name')}
|
||||
value={networkSpecs.title}
|
||||
/>
|
||||
<ChainImg className='settings--networkSpecs-logo' />
|
||||
</div>
|
||||
</td>
|
||||
<td rowSpan={9}>
|
||||
{qrData.genesisHash && (
|
||||
<QrNetworkSpecs
|
||||
className='settings--networkSpecs-qr'
|
||||
networkSpecs={debouncedQrData}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div className='settings--networkSpecs-color'>
|
||||
<div>
|
||||
<Input
|
||||
className='full settings--networkSpecs-colorInput'
|
||||
isError={!_checkColorValid()}
|
||||
label={t('Color')}
|
||||
onChange={_onChangeColor}
|
||||
value={networkSpecs.color}
|
||||
/>
|
||||
<a
|
||||
className='settings--networkSpecs-colorChangeButton'
|
||||
onClick={_onSetRandomColor}
|
||||
>
|
||||
{t('generate random color')}
|
||||
</a>
|
||||
</div>
|
||||
<ChainColorIndicator
|
||||
className='settings--networkSpecs-colorBar'
|
||||
color={networkSpecs.color}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Genesis Hash')}
|
||||
value={networkSpecs.genesisHash}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Unit')}
|
||||
value={networkSpecs.unit}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Address Prefix')}
|
||||
value={networkSpecs.prefix.toString()}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Decimals')}
|
||||
value={networkSpecs.decimals.toString()}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Chain Type')}
|
||||
value={networkSpecs.chainType}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Runtime Version')}
|
||||
value={runtimeVersion ? `${runtimeVersion.specName.toString()}/${runtimeVersion.specVersion.toNumber()}` : ''}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Current Block')}
|
||||
value={blockNumber ? formatNumber(blockNumber) : ''}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</StyledTable>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
td {
|
||||
padding: 0;
|
||||
|
||||
.input.ui--Input input {
|
||||
border: none !important;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
tr {
|
||||
&:first-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings--networkSpecs-name {
|
||||
position: relative;
|
||||
|
||||
.settings--networkSpecs-logo {
|
||||
height: 32px;
|
||||
left: 12px;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings--networkSpecs-color {
|
||||
position: relative;
|
||||
|
||||
> div:first-child {
|
||||
display: flex;
|
||||
|
||||
.settings--networkSpecs-colorInput {
|
||||
min-width: 124px;
|
||||
}
|
||||
|
||||
.settings--networkSpecs-colorChangeButton {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
align-self: flex-end;
|
||||
padding-bottom: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.settings--networkSpecs-colorBar {
|
||||
border-radius: 50%;
|
||||
border: 1px solid grey;
|
||||
height: 32px;
|
||||
left: 12px;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings--networkSpecs-qr {
|
||||
margin: 0.25rem auto;
|
||||
max-width: 15rem;
|
||||
|
||||
img {
|
||||
border: 1px solid white;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(NetworkSpecs);
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BareProps as Props } from '@pezkuwi/react-components/types';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { packageInfo } from '@pezkuwi/apps-config';
|
||||
import { Input, Spinner, styled, Table } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
const appsVersion = `apps v${packageInfo.version.replace('-x', '')}`;
|
||||
|
||||
function SystemVersion ({ className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady, systemName, systemVersion } = useApi();
|
||||
|
||||
const headerRef = useRef<[React.ReactNode?, string?, number?][]>([
|
||||
[t('system version'), 'start', 2]
|
||||
]);
|
||||
|
||||
if (!isApiReady) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledTable
|
||||
className={className}
|
||||
empty={t('No version information available')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Node version')}
|
||||
value={systemName + ' v' + systemVersion}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('API Version')}
|
||||
value={`${api.libraryInfo.replace('@pezkuwi/api ', '')}`}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('Apps Version')}
|
||||
value={`${appsVersion.replace('apps ', '')}`}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</StyledTable>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
td {
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
.input.ui--Input input {
|
||||
border: none !important;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.ui--Labelled-content .ui.input > input {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.ui--Labelled:not(.isSmall) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.ui--Labelled > label, .ui--Labelled > .ui--Labelled-content {
|
||||
left: 0 !important;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(SystemVersion);
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface Option {
|
||||
text: React.ReactNode;
|
||||
value: number | string;
|
||||
}
|
||||
|
||||
export default function itemOption (label: string, value: string | number, img: string): Option {
|
||||
return {
|
||||
text: (
|
||||
<div
|
||||
className='ui--Dropdown-item'
|
||||
key={value}
|
||||
>
|
||||
<img
|
||||
alt={label}
|
||||
className='ui--Dropdown-icon'
|
||||
src={img}
|
||||
/>
|
||||
<div className='ui--Dropdown-name'>{label}</div>
|
||||
</div>
|
||||
),
|
||||
value
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
|
||||
import useChainInfo from '../useChainInfo.js';
|
||||
import useRawMetadata from '../useRawMetadata.js';
|
||||
import Extensions from './Extensions.js';
|
||||
import NetworkSpecs from './NetworkSpecs.js';
|
||||
import SystemVersion from './SystemVersion.js';
|
||||
|
||||
export default function Metadata (): React.ReactElement {
|
||||
const { isDevelopment } = useApi();
|
||||
const rawMetadata = useRawMetadata();
|
||||
const chainInfo = useChainInfo();
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isDevelopment && (
|
||||
<Extensions
|
||||
chainInfo={chainInfo}
|
||||
rawMetadata={rawMetadata}
|
||||
/>
|
||||
)}
|
||||
<NetworkSpecs chainInfo={chainInfo} />
|
||||
<SystemVersion />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user