Skip to content

Commit

Permalink
chore: move selection parser to app root
Browse files Browse the repository at this point in the history
  • Loading branch information
PainterPuppets committed Mar 28, 2024
1 parent 95f2edf commit d241c91
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 102 deletions.
7 changes: 5 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { DefaultTheme, ThemeProvider } from 'styled-components'
import Routers from './routes'
import Toast from './components/Toast'
import { SelectionParser } from './components/SelectionParser'
import { isMainnet } from './utils/chain'
import { DASQueryContextProvider } from './hooks/useDASAccount'
import { getPrimaryColor, getSecondaryColor } from './constants/common'
Expand All @@ -29,8 +30,10 @@ const App = () => {
<div style={appStyle} data-net={isMainnet() ? 'mainnet' : 'testnet'}>
<QueryClientProvider client={queryClient}>
<DASQueryContextProvider>
<Routers />
<Toast />
<SelectionParser>
<Routers />
<Toast />
</SelectionParser>
</DASQueryContextProvider>
</QueryClientProvider>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react'
import * as Select from '@radix-ui/react-select'
import classnames from 'classnames'
// import ChevronUpIcon from './ChevronUp.svg'
// import ChevronDownIcon from './ChevronDown.svg'

import styles from './select.module.scss'

Expand Down
139 changes: 139 additions & 0 deletions src/components/SelectionParser/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* eslint-disable import/no-extraneous-dependencies */
import React, { ComponentProps, useEffect, useMemo, useRef, useState, createContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Root, Trigger, Portal, Content } from 'selection-popover'
import Select from './Select'
import styles from './styles.module.scss'

const DECODER = {
utf8: 'UTF-8',
number: 'Number',
// todo: support decode address & Big-Endian
// bigend: 'Big-Endian',
}

type ContextValue = {
container?: HTMLElement | null
}

const SelectionParserContext = createContext<ContextValue>({
container: undefined,
})

export const SelectionParserProvider = SelectionParserContext.Provider

interface Props extends ComponentProps<'div'> {}

export const SelectionParser: React.FC<React.PropsWithChildren<Props>> = ({ children, ...props }) => {
const { t } = useTranslation()
const ref = useRef(null)
const wrapperRef = useRef<HTMLDivElement>(null)
const contentRef = useRef<HTMLDivElement>(null)
const [decoderOpen, setDecoderOpen] = useState(false)
const [selectedText, setSelectedText] = useState('')
const [anchorElement, setAnchorElement] = useState<HTMLElement | undefined>(undefined)
const [decoder, setDecoder] = useState<keyof typeof DECODER>(Object.keys(DECODER)[0] as keyof typeof DECODER)

const prefix0x = (hex: string) => {
if (hex.startsWith('0x')) {
return hex
}

if (hex.startsWith('x')) {
return `0${hex}`
}

return `0x${hex}`
}

const onSelectionChanged = (e: Event) => {
e.stopPropagation()
e.preventDefault()

const selection = window.getSelection()

if (!selection) {
setDecoderOpen(false)
return
}

if (selection.anchorNode !== selection.focusNode) {
setDecoderOpen(false)
return
}

const min = Math.min(selection.anchorOffset ?? 0, selection.focusOffset ?? 0)
const max = Math.max(selection.anchorOffset ?? 0, selection.focusOffset ?? 0)
const text = (selection.anchorNode?.textContent ?? '').slice(min, max)

setSelectedText(text)
setAnchorElement(selection.anchorNode?.parentElement ?? undefined)
}

useEffect(() => {
window.document.addEventListener('selectionchange', onSelectionChanged)
return () => window.document.removeEventListener('selectionchange', onSelectionChanged)
}, [])

const handleOpenChange = (open: boolean) => {
if (!open) {
setDecoderOpen(false)
return false
}

const selection = window.getSelection()
if (!selection) {
setDecoderOpen(false)
return false
}

if (selection.anchorNode !== selection.focusNode) {
setDecoderOpen(false)
return false
}

setDecoderOpen(true)
return true
}

const decodedText = useMemo(() => {
try {
if (decoder === 'utf8') {
const bytes = [...selectedText.matchAll(/[0-9a-f]{2}/g)].map(a => parseInt(a[0], 16))
return new TextDecoder().decode(new Uint8Array(bytes))
}

if (decoder === 'number') {
if (!selectedText) return ''
return BigInt(prefix0x(selectedText)).toString()
}
} catch {
return `Unable to parse ${selectedText} to ${decoder}`
}
}, [decoder, selectedText])

return (
<div ref={wrapperRef} {...props}>
<Root whileSelect openDelay={100} open={decoderOpen} onOpenChange={handleOpenChange}>
<Trigger ref={ref}>{children}</Trigger>
<Portal container={anchorElement}>
<Content ref={contentRef} side="bottom" className={styles.selectionPopover}>
<div className={styles.selectionPopoverHeader}>
{t('transaction.view_data_as')}
<Select
container={contentRef.current}
value={decoder}
onValueChange={e => setDecoder(e as keyof typeof DECODER)}
options={Object.keys(DECODER).map(key => ({
value: key,
label: DECODER[key as keyof typeof DECODER],
}))}
/>
</div>
<div className={styles.selectionPopoverContent}>{decodedText}</div>
</Content>
</Portal>
</Root>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// @import '@radix-ui/colors/black-alpha.css';
// @import '@radix-ui/colors/mauve.css';
// @import '@radix-ui/colors/violet.css';

/* reset */
button {
all: unset;
}
Expand Down Expand Up @@ -30,6 +25,7 @@ button {
background-color: white;
border-radius: 6px;
box-shadow: 0 10px 38px -10px rgb(22 23 24 / 35%), 0 10px 20px -15px rgb(22 23 24 / 20%);
z-index: 3;
}

.selectViewport {
Expand Down
45 changes: 45 additions & 0 deletions src/components/SelectionParser/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@import '../../styles/variables.module';

.selectionPopover {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 10px #eee;
transform-origin: var(--selection-popover-content-transform-origin);
animation: scale-in 500ms cubic-bezier(0.16, 1, 0.3, 1);
max-width: 400px;
word-wrap: break-word;
z-index: 2;
}

.selectionPopoverHeader {
padding: 16px;
border-bottom: 1px solid #e5e5e5;
font-size: 1rem;
font-weight: 500;
text-align: start;
}

.selectionPopoverContent {
padding: 16px;
font-size: 0.875rem;
font-weight: 600;
color: #666;
line-height: 1.5rem;
text-align: start;
}

@keyframes scale-in {
from {
opacity: 0;
transform: scale(0);
}

to {
opacity: 1;
transform: scale(1);
}
}

.notSelect {
user-select: none;
}
90 changes: 0 additions & 90 deletions src/pages/Transaction/TransactionCellScript/CellInfoDataView.tsx

This file was deleted.

3 changes: 1 addition & 2 deletions src/pages/Transaction/TransactionCellScript/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
TransactionCellDetailTitle,
} from './styled'
import SmallLoading from '../../../components/Loading/SmallLoading'
import { CellInfoDataView } from './CellInfoDataView'
import CloseIcon from './modal_close.png'
import config from '../../../config'
import { getBtcUtxo, getContractHashTag } from '../../../utils/util'
Expand Down Expand Up @@ -340,7 +339,7 @@ export default ({ cell, onClose }: TransactionCellScriptProps) => {
{isFetched ? (
<div className="transactionDetailContent">
{selectedInfo === CellInfo.DATA && isCellData(content) ? (
<CellInfoDataView data={content.data} />
<div className={styles.dataView}>{content.data}</div>
) : (
<CellInfoValueJSONView content={content} />
)}
Expand Down

0 comments on commit d241c91

Please sign in to comment.