Skip to content

Commit

Permalink
Feat: emoji identicons (#2305)
Browse files Browse the repository at this point in the history
* Feat: emoji identicons

Add experimental toggle

* Fix tests

* Fix build

* Adjust settings style

* Disable emojis for small avatars

* Extract static fn part

* Fix z-index
  • Loading branch information
katspaugh authored Aug 4, 2023
1 parent e99b511 commit 3410911
Show file tree
Hide file tree
Showing 18 changed files with 182 additions and 58 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ yalc.lock
/public/sw.js.map
/public/workbox-*.js
/public/workbox-*.js.map
/public/fallback*
4 changes: 4 additions & 0 deletions src/components/balances/AssetsTable/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ describe('AssetsTable', () => {
onChainSigning: false,
},
transactionExecution: true,
addressEmojis: false,
},
},
})
Expand Down Expand Up @@ -215,6 +216,7 @@ describe('AssetsTable', () => {
onChainSigning: false,
},
transactionExecution: true,
addressEmojis: false,
},
},
})
Expand Down Expand Up @@ -317,6 +319,7 @@ describe('AssetsTable', () => {
onChainSigning: false,
},
transactionExecution: true,
addressEmojis: false,
},
},
})
Expand Down Expand Up @@ -416,6 +419,7 @@ describe('AssetsTable', () => {
onChainSigning: false,
},
transactionExecution: true,
addressEmojis: false,
},
},
})
Expand Down
1 change: 1 addition & 0 deletions src/components/balances/HiddenTokenButton/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ describe('HiddenTokenToggle', () => {
onChainSigning: false,
},
transactionExecution: true,
addressEmojis: false,
},
},
})
Expand Down
4 changes: 4 additions & 0 deletions src/components/batch/BatchSidebar/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
margin: var(--space-3) 0;
}

.txs {
width: 100%;
}

.txs ul {
padding: 0 var(--space-3) var(--space-3);
display: flex;
Expand Down
6 changes: 5 additions & 1 deletion src/components/common/ConnectWallet/AccountCenter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ChainSwitcher from '../ChainSwitcher'
import useAddressBook from '@/hooks/useAddressBook'
import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
import WalletInfo, { UNKNOWN_CHAIN_NAME } from '../WalletInfo'
import AddressEmoji from '../EthHashInfo/AddressEmoji'

const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)
Expand Down Expand Up @@ -78,7 +79,10 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => {
sx={{ marginTop: 1 }}
>
<Paper className={css.popoverContainer}>
<Identicon address={wallet.address} />
<div className={css.identicon}>
<Identicon address={wallet.address} />
<AddressEmoji address={wallet.address} />
</div>

<Typography variant="h5" className={css.addressName}>
{addressBook[wallet.address] || wallet.ens}
Expand Down
4 changes: 4 additions & 0 deletions src/components/common/ConnectWallet/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
gap: var(--space-2);
}

.identicon {
position: relative;
}

@media (max-width: 599.95px) {
.buttonContainer button {
font-size: 12px;
Expand Down
57 changes: 57 additions & 0 deletions src/components/common/EthHashInfo/AddressEmoji.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { type ReactElement, memo } from 'react'
import { useAppSelector } from '@/store'
import { selectSettings } from '@/store/settingsSlice'
import css from './styles.module.css'

// Define the Unicode ranges for animal, fruit, and vegetable emojis
const unicodeRanges = [
[0x1f400, 0x1f43f],
[0x1f980, 0x1f994],
[0x1f345, 0x1f35f],
[0x1f950, 0x1f96b],
]

// Calculate the total number of emojis
let totalEmojis = 0
for (let range of unicodeRanges) {
totalEmojis += range[1] - range[0] + 1
}

function ethereumAddressToEmoji(address: string): string {
// Convert the Ethereum address from hexadecimal to decimal
const decimal = BigInt(address.slice(0, 6))

// Calculate the index by taking the modulo of the decimal by the number of emojis
let index = Number(decimal % BigInt(totalEmojis))

// Find the corresponding emoji
for (let range of unicodeRanges) {
if (index < range[1] - range[0] + 1) {
return String.fromCodePoint(range[0] + index)
} else {
index -= range[1] - range[0] + 1
}
}

return ''
}

type EmojiProps = {
address: string
size?: number
}

export const Emoji = memo(function Emoji({ address, size = 40 }: EmojiProps): ReactElement {
return (
<div className={css.emojiWrapper} style={{ fontSize: `${size * 0.7}px`, width: `${size}px`, height: `${size}px` }}>
{ethereumAddressToEmoji(address)}
</div>
)
})

const AddressEmoji = (props: EmojiProps): ReactElement | null => {
const { addressEmojis } = useAppSelector(selectSettings)
return addressEmojis ? <Emoji {...props} /> : null
}

export default AddressEmoji
2 changes: 1 addition & 1 deletion src/components/common/EthHashInfo/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ describe('EthHashInfo', () => {

expect(container.querySelector('.icon')).toHaveAttribute(
'style',
`background-image: url(${makeBlockie(MOCK_SAFE_ADDRESS)}); width: 40px; height: 40px;`,
`background-image: url(${makeBlockie(MOCK_SAFE_ADDRESS)}); width: 44px; height: 44px;`,
)
})

Expand Down
33 changes: 20 additions & 13 deletions src/components/common/EthHashInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import useChainId from '@/hooks/useChainId'
import { useAppSelector } from '@/store'
import { selectSettings } from '@/store/settingsSlice'
import { selectChainById } from '@/store/chainsSlice'

import { getBlockExplorerLink } from '../../../utils/chains'

import { getBlockExplorerLink } from '@/utils/chains'
import type { EthHashInfoProps } from '@safe-global/safe-react-components'
import css from './styles.module.css'
import { Emoji } from './AddressEmoji'

const PrefixedEthHashInfo = ({
showName = true,
avatarSize = 44,
...props
}: EthHashInfoProps & { showName?: boolean }): ReactElement => {
const settings = useAppSelector(selectSettings)
Expand All @@ -20,18 +21,24 @@ const PrefixedEthHashInfo = ({
const addressBook = useAddressBook()
const link = chain ? getBlockExplorerLink(chain, props.address) : undefined
const name = showName ? props.name || addressBook[props.address] : undefined
const showEmoji = settings.addressEmojis && props.showAvatar !== false && !props.customAvatar && avatarSize >= 20

return (
<EthHashInfo
prefix={chain?.shortName}
showPrefix={settings.shortName.show}
copyPrefix={settings.shortName.copy}
{...props}
name={name}
ExplorerButtonProps={{ title: link?.title || '', href: link?.href || '' }}
>
{props.children}
</EthHashInfo>
<div className={css.container}>
<EthHashInfo
prefix={chain?.shortName}
showPrefix={settings.shortName.show}
copyPrefix={settings.shortName.copy}
{...props}
name={name}
customAvatar={props.customAvatar}
ExplorerButtonProps={{ title: link?.title || '', href: link?.href || '' }}
avatarSize={avatarSize}
>
{props.children}
</EthHashInfo>
{showEmoji && <Emoji address={props.address} size={avatarSize} />}
</div>
)
}

Expand Down
50 changes: 14 additions & 36 deletions src/components/common/EthHashInfo/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,41 +1,19 @@
.container {
display: flex;
align-items: center;
gap: 0.5em;
line-height: 1.4;
}

.avatar {
flex-shrink: 0;
position: relative;
}

.resizeAvatar,
.resizeAvatar * {
width: 2.3em !important;
height: 2.3em !important;
}

.addressRow {
.emojiWrapper {
text-align: center;
display: flex;
align-items: center;
gap: 0.25em;
white-space: nowrap;
}

.nameRow {
overflow: hidden;
}

.mobileAddress {
display: none;
}

@media (max-width: 599.95px) {
.mobileAddress {
display: inline-block;
}

.desktopAddress {
display: none;
}
flex-direction: column;
justify-content: center;
position: absolute;
top: 0;
left: 0;
border-radius: 100%;
border: 2px solid var(--color-secondary-light);
color: #000;
background-color: rgba(255, 255, 255, 0.5);
text-shadow: -1px 0 0 var(--color-secondary-light), 0 -1px 0 var(--color-secondary-light),
1px 0 0 var(--color-secondary-light), 0 1px 0 var(--color-secondary-light);
}
2 changes: 2 additions & 0 deletions src/components/common/SafeIcon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box } from '@mui/material'

import css from './styles.module.css'
import Identicon, { type IdenticonProps } from '../Identicon'
import AddressEmoji from '../EthHashInfo/AddressEmoji'

interface ThresholdProps {
threshold: number | string
Expand All @@ -24,6 +25,7 @@ const SafeIcon = ({ address, threshold, owners, size }: SafeIconProps): ReactEle
<div className={css.container}>
{threshold && owners ? <Threshold threshold={threshold} owners={owners} /> : null}
<Identicon address={address} size={size} />
<AddressEmoji address={address} size={size} />
</div>
)

Expand Down
2 changes: 1 addition & 1 deletion src/components/common/SafeIcon/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
position: absolute;
top: -6px;
right: -6px;
z-index: 1;
z-index: 2;
border-radius: 100%;
font-size: 12px;
min-width: 24px;
Expand Down
23 changes: 23 additions & 0 deletions src/components/settings/EmojiPreview/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Alert, Box, Chip, SvgIcon, Typography } from '@mui/material'
import { ZERO_ADDRESS } from '@safe-global/safe-core-sdk/dist/src/utils/constants'
import InfoIcon from '@/public/images/notifications/info.svg'
import SafeIcon from '@/components/common/SafeIcon'

const EmojiPreview = () => (
<>
<Chip label="New" color="secondary" sx={{ fontWeight: 'bold', borderRadius: 2 }} />

<Alert severity="success" sx={{ marginTop: 2, borderColor: 'secondary.main' }} icon={<></>}>
<SvgIcon component={InfoIcon} sx={{ marginRight: 1, verticalAlign: 'middle' }} color="secondary" />

<Typography component="span">Enable emojis for all Ethereum addresses and your Safe Accounts.</Typography>

<Box mt={1} display="flex" alignItems="center" gap={1}>
<SafeIcon address={ZERO_ADDRESS} />
<Typography variant="body2">{ZERO_ADDRESS}</Typography>
</Box>
</Alert>
</>
)

export default EmojiPreview
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const TransferTxInfo = ({ txInfo, txStatus }: TransferTxInfoProps) => {
<Box>
<TransferTxInfoSummary txInfo={txInfo} txStatus={txStatus} />
<Box display="flex" alignItems="center">
<EthHashInfo address={address} shortAddress={false} hasExplorer showCopyButton>
<EthHashInfo address={address} shortAddress={false} hasExplorer showCopyButton avatarSize={44}>
<TransferActions address={address} txInfo={txInfo} />
</EthHashInfo>
</Box>
Expand Down
2 changes: 1 addition & 1 deletion src/components/tx/SignOrExecuteForm/BatchButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const BatchButton = ({
<Tooltip title={tooltip} placement="top">
<span>
<Track {...BATCH_EVENTS.BATCH_APPEND}>
<Button variant="outlined" onClick={onClick} disabled={disabled} sx={{ display: ['none', 'block'] }}>
<Button variant="outlined" onClick={onClick} disabled={disabled} sx={{ display: ['none', 'flex'] }}>
<SvgIcon component={PlusIcon} inheritViewBox fontSize="small" sx={{ mr: 1 }} />
Add to batch
</Button>
Expand Down
Loading

0 comments on commit 3410911

Please sign in to comment.