ghost-keychain/packages/extension-ui/src/components/Address.tsx
Ivan Rukhavets 85db56cd0f Redesign (#203)
* Inject theme using styled-components' ThemeProvider (#3)

* Apply colors from design, styles refactoring (#5)

* Add new tip and accounts screen without accounts (#7)

* Sync with upstream (#10)

* Use Nunito font (#11)

* Create account first page (#14)

* Customizable identicon (#13)

* Inline buttons (#16)

* Welcome screen styling (#17)

* Account creation second step (#15)

* Add new header (#20)

* change welcome screen (#19)

* Account screen (#18)

* Update from upstream (#21)

* Window title (#25)

* New authorize (#24)

* Refactor and remove unnecessary stuff from theme (#22)

* refactor add  accounts screen (#29)

* Export and forget screens, change general padding to 24px (#27)

* Signing screen (#26)

* Import account screens (#31)

* Change header with settings (#28)

* Refactor button and mnemonic (#30)

* Restyle checkbox (#33)

* Remove opacity from background; add borders (#32)

* Code style polishing (#34)

* Move fileMock to __mocks__ (#35)

* Add light theme (#36)

* Small design changes (#38)

* Change background color (#39)

* Sync with upstream (#41)

* Update screenshots in readme

* [CI Skip]  0.14.0-beta.7

skip-checks: true

* fix error for display menu (#42)

* fix display of authorization (#43)

* firefox scrol (#45)

* small change for rename account (#44)

* Sync with upstream

* Print and back button (#48)

* delete print button and change cancel to back button

* Set body color (#49)

* Fix the fix of account rename (#52)

* Update generic sign screen (#47)

* Improve UI/UX design (#53)

* [CI Skip]  0.14.0-beta.10

skip-checks: true

* Improve QR code recognition on dark theme (#54)

* Add proper tx/sign texts and headers (#55)

Co-authored-by: Kamil Dąbrowski <kamil@ethworks.io>
2019-12-20 10:06:43 +01:00

237 lines
6.4 KiB
TypeScript

// Copyright 2019 @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 { AccountJson } from '@polkadot/extension/background/types';
import { Chain } from '@polkadot/extension-chains/types';
import React, { useContext, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import findChain from '@polkadot/extension-chains';
import settings from '@polkadot/ui-settings';
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
import { AccountContext } from './contexts';
import Identicon from '@polkadot/extension-ui/components/Identicon';
import Svg from '@polkadot/extension-ui/components/Svg';
import Menu from '@polkadot/extension-ui/components/Menu';
import DetailsImg from '../assets/details.svg';
import { useOutsideClick } from '@polkadot/extension-ui/hooks';
interface Props {
address?: string | null;
className?: string;
name?: React.ReactNode | null;
children?: React.ReactNode;
genesisHash?: string | null;
actions?: React.ReactNode;
}
// find an account in our list
function findAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJson | null {
const pkStr = publicKey.toString();
return accounts.find(({ address }): boolean =>
decodeAddress(address).toString() === pkStr
) || null;
}
// 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] {
// 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);
return [
// always allow the actual settings to override the display
encodeAddress(publicKey, settings.prefix === -1 ? chain.ss58Format : settings.prefix),
account,
chain
];
}
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 [showActionsMenu, setShowActionsMenu] = useState(false);
const [moveMenuUp, setIsMovedMenu] = useState(false);
const actionsRef = useRef<HTMLDivElement>(null);
useOutsideClick(actionsRef, () => (showActionsMenu && setShowActionsMenu(!showActionsMenu)));
useEffect((): void => {
if (!address) {
return;
}
const [formatted, account, chain] = recodeAddress(address, accounts, genesisHash);
setFormatted(formatted);
setChain(chain);
setAccount(account);
}, [address]);
useEffect(() => {
if (!showActionsMenu) {
setIsMovedMenu(false);
return;
}
if (!actionsRef.current) {
return;
}
const { bottom } = actionsRef.current.getBoundingClientRect();
if (bottom > ACCOUNTS_SCREEN_HEIGHT) {
setIsMovedMenu(true);
}
}, [showActionsMenu]);
const theme = ((chain && chain.icon) || 'polkadot') as 'polkadot';
return (
<div className={className}>
<div>
<AccountInfoRow>
<Identicon
iconTheme={theme}
value={address}
/>
<Info>
<Name>{name || (account && account.name) || '<unknown>'}</Name>
<FullAddress>{formatted || '<unknown>'}</FullAddress>
{chain?.genesisHash && <Banner>{chain.name}</Banner>}
</Info>
{actions && (
<>
<Settings onClick={(): void => setShowActionsMenu(!showActionsMenu)}>
{showActionsMenu ? <ActiveActionsIcon /> : <ActionsIcon />}
</Settings>
{showActionsMenu && <MovableMenu reference={actionsRef} isMoved={moveMenuUp}>{actions}</MovableMenu>}
</>
)}
</AccountInfoRow>
{children}
</div>
</div>
);
}
const AccountInfoRow = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
height: 72px;
border-radius: 4px;
`;
const Info = styled.div`
width: 100%;
`;
const Name = styled.div`
overflow: hidden;
text-overflow: ellipsis;
width: 300px;
margin: 2px 0;
font-weight: 600;
font-size: 16px;
line-height: 22px;
`;
const FullAddress = styled.div`
width: 300px;
overflow: hidden;
text-overflow: ellipsis;
color: ${({ theme }): string => theme.labelColor};
font-size: 12px;
line-height: 16px;
`;
FullAddress.displayName = 'FullAddress';
const Settings = styled.div`
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 40px;
& ${Svg} {
width: 3px;
height: 19px;
}
&:before {
content: "";
position: absolute;
left: 0;
top: 25%;
bottom: 25%;
width: 1px;
background: ${({ theme }): string => theme.boxBorderColor};
}
&:hover {
cursor: pointer;
background: ${({ theme }): string => theme.readonlyInputBackground};
}
`;
Settings.displayName = 'Details';
const ActionsIcon = styled(Svg).attrs(() => ({
src: DetailsImg
}))`
background: ${({ theme }): string => theme.accountDotsIconColor};
`;
const ActiveActionsIcon = styled(Svg).attrs(() => ({
src: DetailsImg
}))`
background: ${({ theme }): string => theme.primaryColor};
`;
const Banner = styled.div`
background: ${({ theme }): string => theme.primaryColor};
border-radius: 0 0 8px 8px;
color: white;
font-size: 12px;
line-height: 16px;
padding: 0.1rem 0.5rem;
position: absolute;
right: 40px;
top: 0;
`;
const MovableMenu = styled(Menu)<{ isMoved: boolean }>`
${({ isMoved }): string => isMoved ? 'bottom: 50px' : ''};
`;
export default styled(Address)`
position: relative;
margin-bottom: 8px;
& > div {
background: ${({ theme }): string => theme.accountBackground};
border: 1px solid ${({ theme }): string => theme.boxBorderColor};
box-sizing: border-box;
border-radius: 4px;
}
& ${Identicon} {
margin-left: 25px;
margin-right: 10px;
& svg {
width: 50px;
height: 50px;
}
}
`;