Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(xudt-tag): add xudt tag column in xudt page #378

Merged
merged 21 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ac7c451
feat(xudt-tag): add xudt tag column in xudt page
Daryl-L Jun 13, 2024
d0565ea
feat(xudt): add filter to filter tags
Daryl-L Jun 24, 2024
afedc9c
fix(multi-filter-button): use filter name
Daryl-L Jun 26, 2024
1255041
style(multi-filter-button): use URLSearchParams
Daryl-L Jun 26, 2024
147bd6e
feat(multi_button_filter): use i18n for select
Daryl-L Jun 26, 2024
a8cb2c7
style(xudt-tag): move function to object for filter item list
Daryl-L Jun 27, 2024
fdff98b
feat(tag-filter): can select all
Daryl-L Jul 1, 2024
d53ab25
style(xudt-tags): add `isAllSelected` and `isNoneSelected`
Daryl-L Jul 2, 2024
dd9cab8
style(multi-filter-button): filteredList -> filterList
Daryl-L Jul 2, 2024
8393116
fix(multi-filter-button): lost other parameters
Daryl-L Jul 2, 2024
74c9baa
feat(xudt-tag): no record when not choose any tags
Daryl-L Jul 2, 2024
26dc5bb
fix(xudt-tag): remove category
Daryl-L Jul 9, 2024
b548c85
style(xudt): use `!!` instead of `!== ''`
Daryl-L Jul 9, 2024
567643c
feat(xudt): use union params
Daryl-L Jul 9, 2024
3fce98f
feat(xudt-tag): remove category
Daryl-L Jul 10, 2024
49c6cc5
feat(xudt-tag): add tag
Daryl-L Jul 10, 2024
a0bc5d7
feat(xudt-tag): add and remove tags
Daryl-L Jul 10, 2024
d2e1d60
feat(xudt-tags): multi select remove page parameter
Daryl-L Jul 10, 2024
a573a1c
feat(xudt-tags): mobile empty for not select any tags
Daryl-L Jul 10, 2024
bea35d6
feat(xudt-tags): only 1 page when not select any tags
Daryl-L Jul 10, 2024
b4b27d1
feat(multi-filter): highlight
Daryl-L Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/assets/not-selected-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/assets/partial-selected-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/selected-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
103 changes: 103 additions & 0 deletions src/components/MultiFilterButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Link, useLocation } from 'react-router-dom'
import { Popover } from 'antd'
import { useTranslation } from 'react-i18next'
import { ReactComponent as FilterIcon } from '../../assets/filter_icon.svg'
import { ReactComponent as SelectedIcon } from '../../assets/selected-icon.svg'
import { ReactComponent as NotSelectedIcon } from '../../assets/not-selected-icon.svg'
import { ReactComponent as PartialSelectedIcon } from '../../assets/partial-selected-icon.svg'
import { useSearchParams } from '../../hooks'
import styles from './styles.module.scss'

export function MultiFilterButton({
filterList,
isMobile,
filterName,
}: {
filterName: string
filterList: { key: string; value: string; to: string; title: string | JSX.Element }[]
isMobile?: boolean
}) {
const { t } = useTranslation()
const params = useSearchParams(filterName)
const filter = params[filterName]
let types = filterList.map(f => f.value)
if (filter !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be simplified as

const types = params[filterName]?.split(',').filter(t => !t)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filterName should be filterNames if it's designed to be multiple filters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, the filters from the URL are more like filteredList because they are effective/working filters while filteredList in the component params is a list of filters.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filterName should be filterNames if it's designed to be multiple filters.

I think there would be only one filter for each filter button, so I think "s" is not necessary here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three values for the filterName.

  1. undefined, which means to entry the page without any filter parameters, for "all selected".
  2. Empty string '', like tags= which means "not selected any value".
  3. Partial selected.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three values for the filterName.

  1. undefined, which means to entry the page without any filter parameters, for "all selected".
  2. Empty string '', like tags= which means "not selected any value".
  3. Partial selected.
const types = params[filterName]?.split(',').filter(t => !t)

works well for these 3 cases

when params[filterName] is undefined, types is undefined, means all selected
when params[filterName] is an empty string, types is an empty array, means not selected any value
when params[filterName] is a string, types is expected to be an array with items, means some selected

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any update on this conversation?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filterName should be filterNames if it's designed to be multiple filters.

I think there would be only one filter for each filter button, so I think "s" is not necessary here.

The logic is as follows

const filter = params[filterName]
const types = filter.split(',').filter(t => t !== '')

equivalent to

const types = params[filterName].split(',').filter(t => t !== '')

So params[filterName] is expected to be a string like A,B,C, which includes multiple values. That's the reason I think it should be filterNames

The filterName means the name of the filter, and A,B,C should be the values of this filter, not the name.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three values for the filterName.

  1. undefined, which means to entry the page without any filter parameters, for "all selected".
  2. Empty string '', like tags= which means "not selected any value".
  3. Partial selected.
const types = params[filterName]?.split(',').filter(t => !t)

works well for these 3 cases

when params[filterName] is undefined, types is undefined, means all selected when params[filterName] is an empty string, types is an empty array, means not selected any value when params[filterName] is a string, types is expected to be an array with items, means some selected

The result of !t is true when it is an empty string, but the empty string should filtered to the result

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My typo, .filter(t => !t) should be .filter(t => !!t) to return truty values

types = filter.split(',').filter(t => !!t)
}

const isAllSelected = types.length === filterList.length
const isNoneSelected = types.length === 0
const search = new URLSearchParams(useLocation().search)
search.delete(filterName)
search.delete('page')

return (
<Popover
className={styles.container}
placement="bottomRight"
trigger={isMobile ? 'click' : 'hover'}
overlayClassName={styles.antPopover}
content={
<div className={styles.filterItems}>
<div className={styles.selectTitle}>
<h2>{t('components.multi_filter_button.select')}</h2>
<Link
key="all"
to={() => {
const newSearch = new URLSearchParams(search)
if (isNoneSelected) {
newSearch.append(filterName, filterList.map(f => f.value).join(','))
}

if (isAllSelected) {
newSearch.append(filterName, '')
}

return `${filterList[0].to}?${newSearch.toString()}`
}}
>
{types.length > 0 ? (
<>{isAllSelected ? <SelectedIcon /> : <PartialSelectedIcon />}</>
) : (
<NotSelectedIcon />
)}
</Link>
</div>
{filterList.map(f => (
<Link
key={f.key}
to={() => {
const subTypes = new Set(types)
if (subTypes.has(f.value)) {
subTypes.delete(f.value)
} else {
subTypes.add(f.value)
}

const newSearch = new URLSearchParams(search)
newSearch.append(filterName, Array.from(subTypes).join(','))
return `${f.to}?${newSearch.toString()}`
}}
data-is-active={types.includes(f.value)}
>
{f.title}
{types.includes(f.value) ? <SelectedIcon /> : <NotSelectedIcon />}
</Link>
))}
</div>
}
>
<FilterIcon
className={styles.filter}
// if the filter is the empty string, display highlight
// if the filter is the string list, display highlight
// if the filter is undefined, not display highlight
data-changed={filter !== undefined}
/>
</Popover>
)
}

MultiFilterButton.displayName = 'MultiFilterButton'

export default MultiFilterButton
79 changes: 79 additions & 0 deletions src/components/MultiFilterButton/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.container {
appearance: none;
border: none;
outline: none;
background: none;
display: inline-flex;
vertical-align: text-top;
margin-left: 8px;
cursor: pointer;
}

.antPopover {
:global {
/* stylelint-disable-next-line selector-class-pattern */
.ant-popover-inner {
border-radius: 8px;
box-shadow: 0 2px 10px 0 #eee;
}

/* stylelint-disable-next-line selector-class-pattern */
.ant-popover-inner-content {
padding: 14px 24px 14px 16px;
}
}
}

.filter {
margin-left: 8px;
color: #999;

&[data-changed='true'] {
color: var(--primary-color);
}
}

.filterItems {
display: flex;
flex-direction: column;
width: 200px;

.selectTitle {
color: var(--primary-color);

h2 {
color: #333;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: normal;
}

display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
border-radius: 8px;

a {
padding: 0;
}
}

a {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
border-radius: 8px;

svg {
color: var(--primary-color);
}

&:hover {
background: var(--primary-hover-bg-color);
cursor: pointer;
}
}
}
2 changes: 1 addition & 1 deletion src/components/XUDTTag/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const XUDTTag = ({ tagName }: { tagName: string }) => {
const { push } = useHistory()

let tag = tagName
let content = t(`xudt.${tag}`)
let content = t(`xudt.tags.${tag}`)
if (tag.startsWith('verified-on-')) {
// FIXME: should be i18n
content = content.replace('Platform', tag.replace('verified-on-', ''))
Expand Down
44 changes: 13 additions & 31 deletions src/components/XUDTTag/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,25 @@
&[data-type='supply-limited'] {
border-color: #fcdeb0;
background: #fdf5d7;

span {
color: #caac0f;
}
color: #caac0f;
}

&[data-type='out-of-length-range'] {
border-color: #b0cbfc;
background: #d7e5fd;

span {
color: #346dff;
}
color: #346dff;
}

&[data-type='invalid'] {
border-color: #ccc;
background: #f0f0f0;

span {
color: #666;
}
color: #666;
}

&[data-type='duplicate'] {
border-color: #ffdba6;
background: #fffcf2;

span {
color: #ffa800;
}
color: #ffa800;
}

&[data-type='layer-1-asset'] {
Expand All @@ -63,37 +51,31 @@
&[data-type='verified-on-platform'] {
border-color: #99e6ca;
background: #d7fdf2;
color: #00bb8e;
}

span {
color: #00bb8e;
}
&[data-type='unnamed'] {
border: 1px solid #e5e5e5;
background: #fafafa;
color: #999;
}

&[data-type='supply-unlimited'] {
border-color: #b0e1fc;
background: #d2f7ff;

span {
color: #00a7cc;
}
color: #00a7cc;
}

&[data-type='rgbpp-compatible'] {
border: none;
background: linear-gradient(90deg, #ffd176 0.23%, #ffdb81 6.7%, #84ffcb 99.82%);

span {
color: #333;
}
color: #333;
}

&[data-type='suspicious'] {
background: #ffe8e8;
border-color: #ff9c9c;

span {
color: #fa504f;
}
color: #fa504f;
}

span {
Expand Down
32 changes: 22 additions & 10 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
"handling-reorg": "Reorg detected at {{time}} so CKB Explorer is checking data thoroughly. It may take half an hour, please visit later",
"migration-notice": "Service of CKB explorer will have a migration on {{time}}, and the server might be unavailable temporarily"
},
"components": {
"multi_filter_button": {
"select": "Select"
}
},
"navbar": {
"wallet": "Wallet",
"charts": "Charts",
Expand Down Expand Up @@ -208,6 +213,7 @@
"estimated_epoch_time": "Estimated Epoch Time",
"transactions_per_minute": "Transactions per Minute",
"transactions_last_24hrs": "Transactions in Last 24hrs",
"tags": "Tags",
"nervos_dao": "Nervos DAO",
"mainnet": "Lina Mainnet",
"testnet": "Testnet"
Expand Down Expand Up @@ -757,6 +763,9 @@
"repeat_inscription_symbol": "This inscription is a duplicate with an earlier release of the same symbol."
},
"xudt": {
"title": {
"tags": "Tags"
},
"holder_allocation": "Holder Allocation",
"holder_allocation_description": "There are {{ckb}} CKB Holder and {{btc}} BTC holder of the current asset.",
"lock_hash": "Lock Hash",
Expand All @@ -777,16 +786,19 @@
"address_count": "Address Count",
"created_time": "Created Time",
"tokens_empty": "There are no xUDTs at this time.",
"invalid": "Invalid",
"suspicious": "Suspicious",
"out-of-length-range": "Out Of Length Range",
"duplicate": "Duplicate",
"layer-1-asset": "Layer 1 Asset",
"layer-2-asset": "Layer 2 Asset",
"verified-on": "Verified On Platform",
"supply-limited": "Supply Limited",
"supply-unlimited": "Supply Unlimited",
"rgbpp-compatible": "RGB++ Compatible",
"tags": {
"invalid": "Invalid",
"suspicious": "Suspicious",
"out-of-length-range": "Out Of Length Range",
"duplicate": "Duplicate",
"layer-1-asset": "Layer 1 Asset",
"layer-2-asset": "Layer 2 Asset",
"verified-on": "Verified On Platform",
"supply-limited": "Supply Limited",
"supply-unlimited": "Supply Unlimited",
"rgbpp-compatible": "RGB++ Compatible",
"unnamed": "Unnamed"
},
"category": "Category"
},
"nft": {
Expand Down
Loading