Display IdentityIcon with chain prefix (#218)

* Display IdentitIcon with chain prefix

* Cleanups, make it actually work
This commit is contained in:
Jaco Greeff
2020-01-27 12:28:47 +01:00
committed by GitHub
parent 8b99b107a5
commit 2c017d17b2
10 changed files with 66 additions and 150 deletions
@@ -23,28 +23,24 @@ type Props = {};
export default function Accounts (): React.ReactElement<Props> {
const accounts = useContext(AccountContext);
const mediaAllowed = useContext(MediaContext);
return (
<>
{
(accounts.length === 0)
? <AddAccount />
: (
<>
<Header showSettings text={'Accounts'} />
<AccountsArea>
<>
{
accounts.map((json, index): React.ReactNode => (
<Account
{...json}
key={`${index}:${json.address}`}
/>
))
}
</>
</AccountsArea>
</>
)
{(accounts.length === 0)
? <AddAccount />
: (
<>
<Header showSettings text={'Accounts'} />
<AccountsArea>
{accounts.map((json, index): React.ReactNode => (
<Account
{...json}
key={`${index}:${json.address}`}
/>
))}
</AccountsArea>
</>
)
}
<ButtonArea>
<ButtonWithSubtitle
@@ -1,68 +0,0 @@
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useState } from 'react';
import settings from '@polkadot/ui-settings';
import { setSS58Format } from '@polkadot/util-crypto';
import { Button, Dropdown } from '../../components';
import { windowOpen } from '../../messaging';
import { Back } from '../../partials';
interface Option {
text: string;
value: string;
}
// There are probably better ways, but since we set the popup size, use that
const isPopup = window.innerWidth <= 480;
const cameraOptions = settings.availableCamera.map(({ text, value }): Option => ({ text, value: `${value}` }));
const prefixOptions = settings.availablePrefixes.map(({ text, value }): Option => ({
text: value === -1
? 'Default (Substrate or as specified)'
: text,
value: `${value}`
}));
export default function Settings (): React.ReactElement<{}> {
const [camera, setCamera] = useState(settings.camera);
const [prefix, setPrefix] = useState(`${settings.prefix}`);
const _onChangeCamera = (camera: string): void => {
setCamera(camera);
settings.set({ camera });
};
// FIXME check against index, we need a better solution
const _onChangePrefix = (value: string): void => {
const prefix = parseInt(value, 10);
setSS58Format(prefix === -1 ? 42 : prefix);
setPrefix(value);
settings.set({ prefix });
};
return (
<div>
<Back />
<Dropdown
label='display addresses formatted for'
onChange={_onChangePrefix}
options={prefixOptions}
value={`${prefix}`}
/>
<Dropdown
label='external QR accounts and access'
onChange={_onChangeCamera}
options={cameraOptions}
value={camera}
/>
{isPopup && (
<Button onClick={windowOpen}>Open extension in new window</Button>
)}
</div>
);
}
@@ -19,7 +19,6 @@ import Export from './Export';
import Forget from './Forget';
import ImportQr from './ImportQr';
import ImportSeed from './ImportSeed';
import Settings from './Settings';
import Signing from './Signing';
import Welcome from './Welcome';
@@ -100,7 +99,6 @@ export default function Popup (): React.ReactElement<{}> {
<Route path='/account/export/:address' component={Export} />
<Route path='/account/import-qr' component={ImportQr} />
<Route path='/account/import-seed' component={ImportSeed} />
<Route path='/settings' component={Settings} />
<Route exact path='/' component={Root} />
</Switch>
</SigningContext.Provider>
@@ -27,6 +27,13 @@ interface Props {
actions?: React.ReactNode;
}
interface Recoded {
account: AccountJson | null;
chain: Chain | null;
formatted: string | null;
prefix: number;
}
// find an account in our list
function findAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJson | null {
const pkStr = publicKey.toString();
@@ -37,29 +44,29 @@ function findAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJs
}
// recodes an supplied address using the prefix/genesisHash, include the actual saved account & chain
function recodeAddress (address: string, accounts: AccountJson[], genesisHash?: string | null): [string, AccountJson | null, Chain] {
function recodeAddress (address: string, accounts: AccountJson[], genesisHash?: string | null): Recoded {
// decode and create a shortcut for the encoded address
const publicKey = decodeAddress(address);
// find our account using the actual publicKey, and then find the associated chain
const account = findAccount(accounts, publicKey);
const chain = findChain((account && account.genesisHash) || genesisHash);
const chain = findChain(account?.genesisHash || genesisHash);
const prefix = settings.prefix === -1 ? chain.ss58Format : settings.prefix;
return [
// always allow the actual settings to override the display
encodeAddress(publicKey, settings.prefix === -1 ? chain.ss58Format : settings.prefix),
// always allow the actual settings to override the display
return {
account,
chain
];
chain,
formatted: encodeAddress(publicKey, prefix),
prefix
};
}
const ACCOUNTS_SCREEN_HEIGHT = 500;
function Address ({ address, className, children, genesisHash, name, actions }: Props): React.ReactElement<Props> {
const accounts = useContext(AccountContext);
const [account, setAccount] = useState<AccountJson | null>(null);
const [chain, setChain] = useState<Chain | null>(null);
const [formatted, setFormatted] = useState<string | null>(null);
const [{ account, chain, formatted, prefix }, setRecoded] = useState<Recoded>({ account: null, chain: null, formatted: null, prefix: 42 });
const [showActionsMenu, setShowActionsMenu] = useState(false);
const [moveMenuUp, setIsMovedMenu] = useState(false);
const actionsRef = useRef<HTMLDivElement>(null);
@@ -68,13 +75,9 @@ function Address ({ address, className, children, genesisHash, name, actions }:
useEffect((): void => {
if (address) {
const [formatted, account, chain] = recodeAddress(address, accounts, genesisHash);
setFormatted(formatted);
setChain(chain);
setAccount(account);
setRecoded(recodeAddress(address, accounts, genesisHash));
}
}, [address]);
}, [accounts, address, genesisHash]);
useEffect(() => {
if (!showActionsMenu) {
@@ -97,17 +100,23 @@ function Address ({ address, className, children, genesisHash, name, actions }:
<AccountInfoRow>
<Identicon
iconTheme={theme}
value={address}
prefix={prefix}
value={formatted || address}
/>
<Info>
<Name>{name || (account && account.name) || '<unknown>'}</Name>
<FullAddress>{formatted || '<unknown>'}</FullAddress>
{chain?.genesisHash && <Banner>{chain.name}</Banner>}
{chain?.genesisHash && (
<Banner>{chain.name}</Banner>
)}
</Info>
{actions && (
<>
<Settings onClick={_onClick}>
{showActionsMenu ? <ActiveActionsIcon /> : <ActionsIcon />}
{showActionsMenu
? <ActiveActionsIcon />
: <ActionsIcon />
}
</Settings>
{showActionsMenu && (
<MovableMenu
@@ -4,14 +4,14 @@
import React, { useRef, useState } from 'react';
import styled from 'styled-components';
import ArrowLeftImage from '../assets/arrowLeft.svg';
import logo from '../assets/pjs.svg';
import gear from '../assets/gear.svg';
import { useOutsideClick } from '../hooks';
import Svg from '@polkadot/extension-ui/components/Svg';
import Settings from './Settings';
import ArrowLeftImage from '../assets/arrowLeft.svg';
import Link from './Link';
import Settings from './Settings';
import Svg from './Svg';
interface Props {
children?: React.ReactNode;
@@ -9,16 +9,18 @@ import Icon from '@polkadot/react-identicon';
interface Props {
iconTheme?: 'beachball' | 'empty' | 'jdenticon' | 'polkadot' | 'substrate';
className?: string;
prefix?: number;
value?: string | null;
}
function Identicon ({ iconTheme, className, value }: Props): React.ReactElement<Props> {
function Identicon ({ iconTheme, className, prefix, value }: Props): React.ReactElement<Props> {
return (
<div className={className}>
<Icon
className='icon'
size={64}
theme={iconTheme}
prefix={prefix}
value={value}
/>
</div>
@@ -31,6 +31,7 @@ const prefixOptions = settings.availablePrefixes.map(({ text, value }): Option =
value: `${value}`
}));
// FIXME This does not belong in components, this is actual functionality
export default function Settings ({ reference }: { reference: React.MutableRefObject<null> }): React.ReactElement {
const [camera, setCamera] = useState(settings.camera === 'on');
const [prefix, setPrefix] = useState(`${settings.prefix}`);
@@ -49,6 +50,8 @@ export default function Settings ({ reference }: { reference: React.MutableRefOb
setPrefix(value);
settings.set({ prefix });
// FIXME We don't want to reload here
location.reload();
};
const _onChangeTheme = (checked: boolean): void => setTheme(checked ? 'dark' : 'light');
@@ -68,10 +68,13 @@ const lightTheme: Theme = {
export function chooseTheme (): AvailableThemes {
const preferredTheme = localStorage.getItem('theme');
if (preferredTheme) {
return preferredTheme === 'dark' ? 'dark' : 'light';
}
const isLightColorSchemePreferred = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
return isLightColorSchemePreferred ? 'light' : 'dark';
}
+1 -3
View File
@@ -23,9 +23,7 @@ function Back ({ className, to = '/' }: Props): React.ReactElement<Props> {
);
}
const ArrowLeft = styled(Svg).attrs(() => ({
src: ArrowLeftImage
}))`
const ArrowLeft = styled(Svg).attrs(() => ({ src: ArrowLeftImage }))`
width: 12px;
height: 12px;
margin-right: 13px;
+8 -33
View File
@@ -5460,7 +5460,7 @@ debug@3.1.0, debug@~3.1.0:
dependencies:
ms "2.0.0"
debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -5673,7 +5673,7 @@ detect-indent@^5.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
detect-libc@^1.0.2, detect-libc@^1.0.3:
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
@@ -7998,7 +7998,7 @@ humanize-url@^1.0.0:
normalize-url "^1.0.0"
strip-url-auth "^1.0.0"
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -10469,15 +10469,6 @@ nearley@^2.7.10:
randexp "0.4.6"
semver "^5.4.1"
needle@^2.2.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -10626,22 +10617,6 @@ node-notifier@^6.0.0:
shellwords "^0.1.1"
which "^1.3.1"
node-pre-gyp@*:
version "0.14.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
needle "^2.2.1"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4.4.2"
node-releases@^1.1.46:
version "1.1.47"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.47.tgz#c59ef739a1fd7ecbd9f0b7cf5b7871e8a8b591e4"
@@ -10763,7 +10738,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
semver "^5.6.0"
validate-npm-package-name "^3.0.0"
npm-packlist@^1.1.6, npm-packlist@^1.4.4:
npm-packlist@^1.4.4:
version "1.4.8"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
@@ -10795,7 +10770,7 @@ npm-run-path@^4.0.0:
dependencies:
path-key "^3.0.0"
npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2:
npmlog@^4.0.1, npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -12972,7 +12947,7 @@ rimraf@2.6.3:
dependencies:
glob "^7.1.3"
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -13084,7 +13059,7 @@ sass-lookup@^3.0.0:
dependencies:
commander "^2.16.0"
sax@^1.2.4, sax@~1.2.4:
sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -14092,7 +14067,7 @@ tar-stream@^2.0.0:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^4.4.10, tar@^4.4.12, tar@^4.4.2, tar@^4.4.8:
tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
version "4.4.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==