@stellar-expert/ui-framework 1.17.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2
- package/date/date-selector.js +3 -3
- package/filter/editors/account-filter-view.js +20 -0
- package/filter/editors/asset-filter-view.js +9 -0
- package/filter/editors/timestamp-filter-view.js +34 -0
- package/filter/editors/type-filter-view.js +148 -0
- package/filter/filter-editors.js +102 -0
- package/filter/filter-view.js +227 -0
- package/filter/filter.scss +57 -0
- package/index.d.ts +31 -1
- package/index.js +95 -93
- package/package.json +36 -6
- package/stellar/signature-hint-utils.js +3 -3
- package/CLAUDE.md +0 -35
package/README.md
CHANGED
|
@@ -1011,6 +1011,49 @@ React hook for transaction details by ID or hash.
|
|
|
1011
1011
|
|
|
1012
1012
|
---
|
|
1013
1013
|
|
|
1014
|
+
### Filters
|
|
1015
|
+
|
|
1016
|
+
#### `FilterView`
|
|
1017
|
+
|
|
1018
|
+
Composable filter panel for managing search filters with URL query parameter sync. Supports multiple filter types including accounts, assets, operation types, timestamps, offers, and liquidity pools.
|
|
1019
|
+
|
|
1020
|
+
| Prop | Type | Default | Description |
|
|
1021
|
+
|------|------|---------|-------------|
|
|
1022
|
+
| `presetFilter` | `object` | — | Initial base filters always applied |
|
|
1023
|
+
| `fields` | `object` | `{}` | Filter field definitions keyed by field name |
|
|
1024
|
+
| `onChange` | `function` | — | Callback receiving merged preset + user filters |
|
|
1025
|
+
|
|
1026
|
+
Each entry in `fields` is a `FilterFieldDescriptor`:
|
|
1027
|
+
|
|
1028
|
+
| Property | Type | Default | Description |
|
|
1029
|
+
|----------|------|---------|-------------|
|
|
1030
|
+
| `icon` | `string` | — | Icon class suffix (prepended with `icon-`) |
|
|
1031
|
+
| `title` | `string` | — | Short title in filter condition |
|
|
1032
|
+
| `description` | `string` | — | Tooltip / "Add filter" dropdown text |
|
|
1033
|
+
| `multi` | `boolean` | `true` | Allow multiple values for this field |
|
|
1034
|
+
|
|
1035
|
+
Supported field keys: `type`, `account`, `source`, `destination`, `asset`, `src_asset`, `dest_asset`, `from`, `to`, `memo`, `offer`, `pool`, `topic`.
|
|
1036
|
+
|
|
1037
|
+
```jsx
|
|
1038
|
+
import {FilterView, parseFiltersFromQuery} from '@stellar-expert/ui-framework'
|
|
1039
|
+
|
|
1040
|
+
<FilterView
|
|
1041
|
+
presetFilter={{account: [address]}}
|
|
1042
|
+
fields={{
|
|
1043
|
+
type: {icon: 'hexagon', title: 'Type', description: 'Operation type'},
|
|
1044
|
+
asset: {icon: 'asset', title: 'Asset', description: 'Asset involved'},
|
|
1045
|
+
from: {icon: 'clock', title: 'From', description: 'Start date', multi: false},
|
|
1046
|
+
to: {icon: 'clock', title: 'To', description: 'End date', multi: false}
|
|
1047
|
+
}}
|
|
1048
|
+
onChange={filters => setActiveFilters(filters)}/>
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
#### `parseFiltersFromQuery(fields)`
|
|
1052
|
+
|
|
1053
|
+
Parses and validates filter parameters from the current URL query string against passed field definitions. Returns an object with matched filter key-value pairs.
|
|
1054
|
+
|
|
1055
|
+
---
|
|
1056
|
+
|
|
1014
1057
|
### Effects
|
|
1015
1058
|
|
|
1016
1059
|
#### `EffectDescription`
|
|
@@ -1098,9 +1141,9 @@ Convert a signature hint Buffer to a StrKey mask string.
|
|
|
1098
1141
|
|
|
1099
1142
|
Format a signature hint for display.
|
|
1100
1143
|
|
|
1101
|
-
#### `
|
|
1144
|
+
#### `signatureHintMatchesKey(hint, key)`
|
|
1102
1145
|
|
|
1103
|
-
Check if a hint matches a key.
|
|
1146
|
+
Check if a hint matches a key.
|
|
1104
1147
|
|
|
1105
1148
|
#### `findKeyBySignatureHint(hint, allKeys)`
|
|
1106
1149
|
|
package/date/date-selector.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {useCallback, useEffect, useState} from 'react'
|
|
1
|
+
import React, {forwardRef, useCallback, useEffect, useState} from 'react'
|
|
2
2
|
import {debounce} from 'throttle-debounce'
|
|
3
3
|
import {normalizeDate, toUnixTimestamp} from '@stellar-expert/formatter'
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ import {normalizeDate, toUnixTimestamp} from '@stellar-expert/formatter'
|
|
|
10
10
|
* @param {string} [max] - Maximum allowed date
|
|
11
11
|
* @param {*} [ref]
|
|
12
12
|
*/
|
|
13
|
-
export
|
|
13
|
+
export const DateSelector = forwardRef(({value, onChange, min, max, ...otherProps}, ref) => {
|
|
14
14
|
const [date, setDate] = useState(value ? trimIsoDateSeconds(normalizeDate(value)) : '')
|
|
15
15
|
useEffect(() => {
|
|
16
16
|
if (value) {
|
|
@@ -35,7 +35,7 @@ export function DateSelector({value, onChange, min, max, ref, ...otherProps}) {
|
|
|
35
35
|
return <input type="datetime-local" value={date} className="date-selector condensed" step={60} ref={ref}
|
|
36
36
|
min={min || minSelectableValue} max={max} onChange={e => selectDate(e.target.value)}
|
|
37
37
|
style={{width: '11em', overflow: 'hidden'}} {...otherProps}/>
|
|
38
|
-
}
|
|
38
|
+
})
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Trim seconds and milliseconds for basic ISO date format representation
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, {useCallback, useState} from 'react'
|
|
2
|
+
import {StrKey} from '@stellar/stellar-base'
|
|
3
|
+
import {AccountAddress} from '../../account/account-address'
|
|
4
|
+
import {useAutoFocusRef} from '../../interaction/autofocus'
|
|
5
|
+
|
|
6
|
+
export function AccountEditor({value, setValue}) {
|
|
7
|
+
const [internalValue, setInternalValue] = useState(value || '')
|
|
8
|
+
const onChange = useCallback(function (e) {
|
|
9
|
+
const v = e.target.value.trim()
|
|
10
|
+
setInternalValue(v)
|
|
11
|
+
if (StrKey.isValidEd25519PublicKey(v) || StrKey.isValidContract(v)) {
|
|
12
|
+
setValue(v)
|
|
13
|
+
}
|
|
14
|
+
}, [setValue])
|
|
15
|
+
if (!setValue)
|
|
16
|
+
return <AccountAddress account={value} link={false}/>
|
|
17
|
+
|
|
18
|
+
return <input type="text" value={internalValue} onChange={onChange} ref={useAutoFocusRef}
|
|
19
|
+
style={{width: '26em', maxWidth: '56vw'}} className="condensed"/>
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {AssetLink} from '../../asset/asset-link'
|
|
3
|
+
import {AssetSelector} from '../../asset/asset-selector'
|
|
4
|
+
|
|
5
|
+
export function AssetEditor({value, setValue}) {
|
|
6
|
+
if (!setValue)
|
|
7
|
+
return <AssetLink asset={value} link={false}/>
|
|
8
|
+
return <AssetSelector value={value} onChange={setValue} title={value ? undefined : 'Choose asset'} expanded/>
|
|
9
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, {useCallback, useState} from 'react'
|
|
2
|
+
import {UtcTimestamp} from '../../date/utc-timestamp'
|
|
3
|
+
import {DateSelector} from '../../date/date-selector'
|
|
4
|
+
import {useAutoFocusRef} from '../../interaction/autofocus'
|
|
5
|
+
|
|
6
|
+
export function TimestampEditor({value, setValue, edit}) {
|
|
7
|
+
value = parseInt(value, 10)
|
|
8
|
+
const [internalValue, setInternalValue] = useState(value || undefined)
|
|
9
|
+
|
|
10
|
+
const confirm = useCallback(function () {
|
|
11
|
+
setInternalValue(internalValue => {
|
|
12
|
+
if (internalValue) {
|
|
13
|
+
setValue(internalValue)
|
|
14
|
+
}
|
|
15
|
+
return internalValue
|
|
16
|
+
})
|
|
17
|
+
}, [setValue])
|
|
18
|
+
|
|
19
|
+
const onKeyDown = useCallback(function (e) {
|
|
20
|
+
if (e.key === 'Enter') {
|
|
21
|
+
confirm()
|
|
22
|
+
}
|
|
23
|
+
if (e.key === 'Escape') {
|
|
24
|
+
setValue(value)
|
|
25
|
+
}
|
|
26
|
+
}, [confirm, setValue, value])
|
|
27
|
+
if (!setValue)
|
|
28
|
+
return <UtcTimestamp date={value} className="condensed"/>
|
|
29
|
+
return <>
|
|
30
|
+
<DateSelector value={internalValue} onChange={setInternalValue} onKeyDown={onKeyDown} ref={edit ? useAutoFocusRef : null}/>
|
|
31
|
+
<a href="#" className="icon-ok" onClick={confirm}/>
|
|
32
|
+
|
|
33
|
+
</>
|
|
34
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Dropdown} from '../../controls/dropdown'
|
|
3
|
+
|
|
4
|
+
export function OperationTypeEditor({value, setValue}) {
|
|
5
|
+
if (!setValue) {
|
|
6
|
+
const option = typeEditorOptions.find(opt => opt.value === value)
|
|
7
|
+
return <span>{option ? option.title : 'Unknown operation type'}</span>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return <Dropdown title="Choose operation type" expanded onChange={setValue}
|
|
11
|
+
options={typeEditorOptions.map(({title, value}) => ({value, title}))}/>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const typeEditorOptions = [
|
|
15
|
+
{
|
|
16
|
+
title: 'Group: payments',
|
|
17
|
+
description: 'Including CreateAccount, AccountMerge, Payment, PathPaymentStrictReceive, PathPaymentStrictSend, CreateClaimableBalance, ClaimClaimableBalance, Clawback, ClawbackClaimableBalance, Inflation operations',
|
|
18
|
+
value: 'payments'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
title: 'Group: trustlines',
|
|
22
|
+
description: 'Including ChangeTrust, AllowTrust, SetTrustLineFlags operations',
|
|
23
|
+
value: 'trustlines'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: 'Group: DEX trading',
|
|
27
|
+
description: 'Including ManageSellOffer, ManageBuyOffer, CreatePassiveSellOffer, LiquidityPoolDeposit, LiquidityPoolWithdraw operations',
|
|
28
|
+
value: 'dex'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
title: 'Group: account settings',
|
|
32
|
+
description: 'Including CreateAccount, SetOptions, ChangeTrust, AllowTrust, AccountMerge, Inflation, ManageData, BumpSequence, BeginSponsoringFutureReserves, EndSponsoringFutureReserves, RevokeSponsorship, SetTrustLineFlags operations',
|
|
33
|
+
value: 'settings'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: 'Group: smart contracts',
|
|
37
|
+
description: 'Including InvokeHostFunction, BumpFootprintExpiration, RestoreFootprint operations',
|
|
38
|
+
value: 'soroban'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: 'CreateAccount',
|
|
42
|
+
value: '0'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
title: 'Payment',
|
|
46
|
+
value: '1'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: 'PathPaymentStrictReceive',
|
|
50
|
+
value: '2'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
title: 'ManageSellOffer',
|
|
54
|
+
value: '3'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: 'CreatePassiveSellOffer',
|
|
58
|
+
value: '4'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: 'SetOptions',
|
|
62
|
+
value: '5'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: 'ChangeTrust',
|
|
66
|
+
value: '6'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
title: 'AllowTrust',
|
|
70
|
+
value: '7'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: 'AccountMerge',
|
|
74
|
+
value: '8'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
title: 'Inflation',
|
|
78
|
+
value: '9'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
title: 'ManageData',
|
|
82
|
+
value: '10'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
title: 'BumpSequence',
|
|
86
|
+
value: '11'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: 'ManageBuyOffer',
|
|
90
|
+
value: '12'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
title: 'PathPaymentStrictSend',
|
|
94
|
+
value: '13'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: 'CreateClaimableBalance',
|
|
98
|
+
value: '14'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
title: 'ClaimClaimableBalance',
|
|
102
|
+
value: '15'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: 'BeginSponsoringFutureReserves',
|
|
106
|
+
value: '16'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
title: 'EndSponsoringFutureReserves',
|
|
110
|
+
value: '17'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
title: 'RevokeSponsorship',
|
|
114
|
+
value: '18'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: 'Clawback',
|
|
118
|
+
value: '19'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
title: 'ClawbackClaimableBalance',
|
|
122
|
+
value: '20'
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
title: 'SetTrustLineFlags',
|
|
126
|
+
value: '21'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
title: 'LiquidityPoolDeposit',
|
|
130
|
+
value: '22'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: 'LiquidityPoolWithdraw',
|
|
134
|
+
value: '23'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
title: 'InvokeHostFunction',
|
|
138
|
+
value: '24'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: 'BumpFootprintExpiration',
|
|
142
|
+
value: '25'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
title: 'RestoreFootprint',
|
|
146
|
+
value: '26'
|
|
147
|
+
}
|
|
148
|
+
]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, {useCallback, useState} from 'react'
|
|
2
|
+
import {StrKey} from '@stellar/stellar-base'
|
|
3
|
+
import {shortenString} from '@stellar-expert/formatter'
|
|
4
|
+
import {AssetLink} from '../asset/asset-link'
|
|
5
|
+
import {useAutoFocusRef} from '../interaction/autofocus'
|
|
6
|
+
import {OperationTypeEditor} from './editors/type-filter-view'
|
|
7
|
+
import {AssetEditor} from './editors/asset-filter-view'
|
|
8
|
+
import {AccountEditor} from './editors/account-filter-view'
|
|
9
|
+
import {TimestampEditor} from './editors/timestamp-filter-view'
|
|
10
|
+
|
|
11
|
+
export function TextEditor({value, setValue, mask}) {
|
|
12
|
+
const [internalValue, setInternalValue] = useState(value || '')
|
|
13
|
+
|
|
14
|
+
const onChange = useCallback(function (e) {
|
|
15
|
+
let v = e.target.value.trim()
|
|
16
|
+
if (mask instanceof RegExp) {
|
|
17
|
+
v = v.replace(mask, '')
|
|
18
|
+
}
|
|
19
|
+
if (typeof mask === 'function') {
|
|
20
|
+
v = mask(v)
|
|
21
|
+
}
|
|
22
|
+
setInternalValue(v)
|
|
23
|
+
}, [mask])
|
|
24
|
+
|
|
25
|
+
const confirm = useCallback(function () {
|
|
26
|
+
setInternalValue(internalValue => {
|
|
27
|
+
setValue(internalValue)
|
|
28
|
+
return internalValue
|
|
29
|
+
})
|
|
30
|
+
}, [setValue])
|
|
31
|
+
|
|
32
|
+
const onKeyDown = useCallback(function (e) {
|
|
33
|
+
if (e.key === 'Enter') {
|
|
34
|
+
confirm()
|
|
35
|
+
}
|
|
36
|
+
if (e.key === 'Escape') {
|
|
37
|
+
setValue(value)
|
|
38
|
+
}
|
|
39
|
+
}, [confirm, setValue, value])
|
|
40
|
+
|
|
41
|
+
if (!setValue) {
|
|
42
|
+
value = value.toString()
|
|
43
|
+
return <span>{value.length > 20 ? shortenString(value, 12) : value}</span>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return <>
|
|
47
|
+
<input type="text" value={internalValue} ref={useAutoFocusRef} onChange={onChange} onKeyDown={onKeyDown}
|
|
48
|
+
style={{width: '16em', maxWidth: '50vw'}}/>
|
|
49
|
+
<a href="#" className="icon-ok" onClick={confirm}/>
|
|
50
|
+
</>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function OfferIdEditor({value, setValue}) {
|
|
54
|
+
return <TextEditor value={value} setValue={setValue} mask={/[\D]/g}/>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function LiquidityPoolEditor({value, setValue}) {
|
|
58
|
+
if (!setValue) {
|
|
59
|
+
if (/^[a-f0-9]{64}$/.test(value)) { //remap hex format to pool id format
|
|
60
|
+
try {
|
|
61
|
+
value = StrKey.encodeLiquidityPool(Buffer.from(value, 'hex'))
|
|
62
|
+
} catch (_) {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!StrKey.isValidLiquidityPool(value))
|
|
66
|
+
return <span className="dimmed">(Invalid pool)</span>
|
|
67
|
+
if (value.startsWith('native'))
|
|
68
|
+
return <span className="dimmed">(Native pool)</span>
|
|
69
|
+
return <AssetLink asset={value} link={false}/>
|
|
70
|
+
}
|
|
71
|
+
return <TextEditor value={value} setValue={setValue} mask={/[\W]/g}/>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function MemoEditor({value, setValue}) {
|
|
75
|
+
return <TextEditor value={value} setValue={setValue} mask={v => v.substring(0, 64)}/>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function TopicEditor({value, setValue}) {
|
|
79
|
+
if (!setValue)
|
|
80
|
+
return <code className="word-break">{value}</code>
|
|
81
|
+
return <TextEditor value={value} setValue={setValue}/>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const editorMapping = {
|
|
85
|
+
topic: TopicEditor,
|
|
86
|
+
type: OperationTypeEditor,
|
|
87
|
+
account: AccountEditor,
|
|
88
|
+
source: AccountEditor,
|
|
89
|
+
destination: AccountEditor,
|
|
90
|
+
asset: AssetEditor,
|
|
91
|
+
src_asset: AssetEditor,
|
|
92
|
+
dest_asset: AssetEditor,
|
|
93
|
+
from: TimestampEditor,
|
|
94
|
+
to: TimestampEditor,
|
|
95
|
+
memo: MemoEditor,
|
|
96
|
+
offer: OfferIdEditor,
|
|
97
|
+
pool: LiquidityPoolEditor
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function resolveFilterEditor(field) {
|
|
101
|
+
return editorMapping[field]
|
|
102
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React, {useCallback, useEffect, useState} from 'react'
|
|
2
|
+
import {navigation, parseQuery} from '@stellar-expert/navigation'
|
|
3
|
+
import deepmerge from 'deepmerge'
|
|
4
|
+
import {Dropdown} from '../controls/dropdown'
|
|
5
|
+
import {resolveFilterEditor} from './filter-editors'
|
|
6
|
+
import './filter.scss'
|
|
7
|
+
|
|
8
|
+
let fieldDescriptionMapping = {}
|
|
9
|
+
|
|
10
|
+
function FilterCondition({field, value, setValue, removeFilter, edit}) {
|
|
11
|
+
const updateValue = useCallback(function (value) {
|
|
12
|
+
setValue(field, value)
|
|
13
|
+
//reset cursor when apply filter
|
|
14
|
+
navigation.updateQuery({cursor: undefined})
|
|
15
|
+
}, [field, setValue])
|
|
16
|
+
|
|
17
|
+
const removeValue = useCallback(function () {
|
|
18
|
+
removeFilter(field, value)
|
|
19
|
+
//reset cursor when remove applied filter
|
|
20
|
+
if (!!value) {
|
|
21
|
+
navigation.updateQuery({cursor: undefined})
|
|
22
|
+
}
|
|
23
|
+
}, [field, value, removeFilter])
|
|
24
|
+
|
|
25
|
+
const childProps = {value, edit}
|
|
26
|
+
const filter = fieldDescriptionMapping[field]
|
|
27
|
+
if (edit || filter.multi === false) {
|
|
28
|
+
childProps.setValue = updateValue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const editor = resolveFilterEditor(field)
|
|
32
|
+
const title = !edit ? '' : (field === 'type' ? '' : filter.title)
|
|
33
|
+
return <span className="filter-condition condensed" title={edit ? '' : filter.description}>
|
|
34
|
+
<span className={'icon-' + filter.icon}/>
|
|
35
|
+
{title} {React.createElement(editor, childProps)}
|
|
36
|
+
{removeFilter ?
|
|
37
|
+
<a href="#" className="icon-delete-circle" onClick={removeValue} title="Remove filter"/> :
|
|
38
|
+
!!setValue ? <>  </> : <> </>}
|
|
39
|
+
</span>
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parses and validates filter parameters from the current URL query string
|
|
43
|
+
* @param {object} [fields] - Custom filter field definitions
|
|
44
|
+
* @return {object}
|
|
45
|
+
*/
|
|
46
|
+
export function parseFiltersFromQuery(fields = fieldDescriptionMapping) {
|
|
47
|
+
const params = parseQuery()
|
|
48
|
+
const filters = {}
|
|
49
|
+
let isEmpty = true
|
|
50
|
+
for (const [key, value] of Object.entries(params)) {
|
|
51
|
+
const filterDescriptor = fields[key]
|
|
52
|
+
if (!filterDescriptor)
|
|
53
|
+
continue //skip unrelated query parameters
|
|
54
|
+
filters[key] = value
|
|
55
|
+
isEmpty = false
|
|
56
|
+
}
|
|
57
|
+
return filters
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function FiltersGroup({filters, replaceFilter, removeFilter, edit = false}) {
|
|
61
|
+
if (!filters)
|
|
62
|
+
return null
|
|
63
|
+
return <>{Object.entries(filters).map(([field, values]) => {
|
|
64
|
+
if (!(values instanceof Array)) {
|
|
65
|
+
return <FilterCondition key={field} field={field} value={values} edit={edit} setValue={replaceFilter}
|
|
66
|
+
removeFilter={removeFilter}/>
|
|
67
|
+
}
|
|
68
|
+
if (!values.length)
|
|
69
|
+
return null
|
|
70
|
+
return <React.Fragment key={field}>
|
|
71
|
+
{values.map(value => <FilterCondition key={value} field={field} value={value} edit={edit}
|
|
72
|
+
setValue={replaceFilter} removeFilter={removeFilter}/>)}
|
|
73
|
+
</React.Fragment>
|
|
74
|
+
})}</>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A component for managing search filters
|
|
79
|
+
* @param {object} [presetFilter] - Initial base filters
|
|
80
|
+
* @param {object} [fields] - Custom filter field definitions
|
|
81
|
+
* @param {func} [onChange] - Callback triggered when filters change
|
|
82
|
+
*/
|
|
83
|
+
export function FilterView({presetFilter, fields = {}, onChange}) {
|
|
84
|
+
fieldDescriptionMapping = fields
|
|
85
|
+
const [filters, setFilters] = useState(presetFilter || {})
|
|
86
|
+
const [_, setSerializedFilter] = useState(JSON.stringify(filters))
|
|
87
|
+
|
|
88
|
+
const availableFields = []
|
|
89
|
+
const readyFilters = {}
|
|
90
|
+
const editFilters = {}
|
|
91
|
+
let editorMode = false
|
|
92
|
+
for (const [field, props = {}] of Object.entries(fieldDescriptionMapping)) {
|
|
93
|
+
const values = filters[field]
|
|
94
|
+
if (values !== undefined) {
|
|
95
|
+
if (values instanceof Array) {
|
|
96
|
+
const newValues = readyFilters[field] = []
|
|
97
|
+
for (let i = 0; i < values.length; i++) {
|
|
98
|
+
const value = values[i]
|
|
99
|
+
if (!value) {
|
|
100
|
+
editFilters[field] = [value]
|
|
101
|
+
editorMode = true
|
|
102
|
+
} else if (!presetFilter || !presetFilter[field]?.includes(value)) {
|
|
103
|
+
newValues.push(value)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
if (!values) {
|
|
108
|
+
editFilters[field] = values
|
|
109
|
+
editorMode = true
|
|
110
|
+
} else {
|
|
111
|
+
readyFilters[field] = values
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (props.multi !== false || !values) {
|
|
116
|
+
availableFields.push({
|
|
117
|
+
value: field,
|
|
118
|
+
title: props.description,
|
|
119
|
+
icon: 'icon-' + props.icon
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const updateExternalFilters = useCallback(function (newFilters) {
|
|
125
|
+
setSerializedFilter(prev => {
|
|
126
|
+
const newValue = JSON.stringify(newFilters)
|
|
127
|
+
if (prev !== newValue) {
|
|
128
|
+
setTimeout(() => onChange(deepmerge(presetFilter, newFilters)), 100)
|
|
129
|
+
return newValue
|
|
130
|
+
}
|
|
131
|
+
return prev
|
|
132
|
+
})
|
|
133
|
+
}, [onChange])
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
const queryParams = parseFiltersFromQuery(fields)
|
|
137
|
+
setFilters(queryParams)
|
|
138
|
+
updateExternalFilters(queryParams)
|
|
139
|
+
}, [fields])
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
const replaceFilter = useCallback((field, value) => setTimeout(() => setFilters(prev => {
|
|
143
|
+
const newFilters = {...prev}
|
|
144
|
+
const filter = fieldDescriptionMapping[field] || {}
|
|
145
|
+
//atomic values
|
|
146
|
+
if (filter.multi === false) {
|
|
147
|
+
if (value === null) {
|
|
148
|
+
delete newFilters[field]
|
|
149
|
+
updateExternalFilters(newFilters)
|
|
150
|
+
} else {
|
|
151
|
+
newFilters[field] = value
|
|
152
|
+
if (value) {
|
|
153
|
+
updateExternalFilters(newFilters)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return newFilters
|
|
157
|
+
}
|
|
158
|
+
//multi-values
|
|
159
|
+
let values = newFilters[field]
|
|
160
|
+
if (!values) {
|
|
161
|
+
values = newFilters[field] = []
|
|
162
|
+
}
|
|
163
|
+
if (value !== null) {
|
|
164
|
+
if (!values.includes(value)) {
|
|
165
|
+
if (values[values.length - 1] === '') {
|
|
166
|
+
values.pop()
|
|
167
|
+
}
|
|
168
|
+
values.push(value)
|
|
169
|
+
if (value) {
|
|
170
|
+
updateExternalFilters(newFilters)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
const idx = values.indexOf('')
|
|
175
|
+
if (idx >= 0) {
|
|
176
|
+
values.splice(idx, 1)
|
|
177
|
+
updateExternalFilters(newFilters)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return newFilters
|
|
181
|
+
}), 100), [updateExternalFilters])
|
|
182
|
+
|
|
183
|
+
const removeFilter = useCallback((field, value) => setFilters(prev => {
|
|
184
|
+
const newFilters = {...prev}
|
|
185
|
+
const filter = fieldDescriptionMapping[field] || {}
|
|
186
|
+
//atomic value
|
|
187
|
+
if (filter.multi === false) {
|
|
188
|
+
if (newFilters[field] || newFilters[field] === '') {
|
|
189
|
+
newFilters[field] = undefined
|
|
190
|
+
updateExternalFilters(newFilters)
|
|
191
|
+
}
|
|
192
|
+
return newFilters
|
|
193
|
+
}
|
|
194
|
+
//multi-values
|
|
195
|
+
const values = newFilters[field]
|
|
196
|
+
const idx = values.indexOf(value)
|
|
197
|
+
if (idx >= 0) {
|
|
198
|
+
values.splice(idx, 1)
|
|
199
|
+
updateExternalFilters(newFilters)
|
|
200
|
+
}
|
|
201
|
+
return newFilters
|
|
202
|
+
}), [updateExternalFilters])
|
|
203
|
+
|
|
204
|
+
const addFilter = useCallback(field => {
|
|
205
|
+
replaceFilter(field, '')
|
|
206
|
+
}, [replaceFilter])
|
|
207
|
+
|
|
208
|
+
const title = <span className="nowrap"><span className="icon icon-add-circle"/>Add filter</span>
|
|
209
|
+
|
|
210
|
+
return <div className="op-filters">
|
|
211
|
+
<div className="mobile-only micro-space"/>
|
|
212
|
+
<span className="icon-filter"/> Filters 
|
|
213
|
+
<FiltersGroup filters={presetFilter}/>
|
|
214
|
+
<FiltersGroup filters={readyFilters} replaceFilter={replaceFilter} removeFilter={!editorMode ? removeFilter : null}/>
|
|
215
|
+
{!editorMode ?
|
|
216
|
+
<Dropdown title={title} options={availableFields} onChange={addFilter}/> :
|
|
217
|
+
<div className="micro-space">
|
|
218
|
+
<FiltersGroup filters={editFilters} replaceFilter={replaceFilter} removeFilter={removeFilter} edit/>
|
|
219
|
+
</div>}
|
|
220
|
+
<div className="micro-space"/>
|
|
221
|
+
</div>
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @callback FilterView~OnChange
|
|
226
|
+
* @param {Object} mergedFilters - The combination of preset filters and newly selected values.
|
|
227
|
+
*/
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.op-filters {
|
|
2
|
+
.icon-delete-circle {
|
|
3
|
+
margin-left: 0.1em;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.op-filter-container {
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: flex-start;
|
|
10
|
+
background-color: var(--color-bg);
|
|
11
|
+
border: 1px solid var(--color-contrast-border);
|
|
12
|
+
border-radius: $border-radius-input;
|
|
13
|
+
color: var(--color-text);
|
|
14
|
+
height: 4rem;
|
|
15
|
+
padding: 0.2rem 1rem;
|
|
16
|
+
width: 100%;
|
|
17
|
+
|
|
18
|
+
&:focus {
|
|
19
|
+
border-color: var(--color-highlight);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.filter-condition {
|
|
24
|
+
display: inline-block;
|
|
25
|
+
margin-right: 0.4em;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
|
|
28
|
+
&:before {
|
|
29
|
+
content: '/';
|
|
30
|
+
color: var(--color-highlight);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.icon-delete-circle {
|
|
34
|
+
opacity: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:hover .icon-delete-circle {
|
|
38
|
+
opacity: 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
> input {
|
|
42
|
+
margin-bottom: 0;
|
|
43
|
+
height: 3rem;
|
|
44
|
+
padding: 0 0.4rem;
|
|
45
|
+
|
|
46
|
+
+ .icon-ok {
|
|
47
|
+
display: inline-block;
|
|
48
|
+
visibility: hidden;
|
|
49
|
+
transition: visibility 0.3s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:focus + .icon-ok {
|
|
53
|
+
visibility: visible;
|
|
54
|
+
margin-left: 0.2em;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -1179,6 +1179,36 @@ export const ScValStruct: React.FC<ScValStructProps>;
|
|
|
1179
1179
|
/** Set of primitive ScVal type identifiers */
|
|
1180
1180
|
export const primitiveTypes: Set<string>;
|
|
1181
1181
|
|
|
1182
|
+
// ============================================================================
|
|
1183
|
+
// Filters
|
|
1184
|
+
// ============================================================================
|
|
1185
|
+
|
|
1186
|
+
export interface FilterFieldDescriptor {
|
|
1187
|
+
/** Icon CSS class suffix (prepended with "icon-") */
|
|
1188
|
+
icon: string;
|
|
1189
|
+
/** Short title displayed in the filter condition */
|
|
1190
|
+
title: string;
|
|
1191
|
+
/** Description shown in the "Add filter" dropdown and as tooltip */
|
|
1192
|
+
description: string;
|
|
1193
|
+
/** Whether multiple values are allowed for this field (default true) */
|
|
1194
|
+
multi?: boolean;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
export interface FilterViewProps {
|
|
1198
|
+
/** Initial base filters that are always applied */
|
|
1199
|
+
presetFilter?: Record<string, any>;
|
|
1200
|
+
/** Filter field definitions keyed by field name */
|
|
1201
|
+
fields?: Record<string, FilterFieldDescriptor>;
|
|
1202
|
+
/** Callback triggered when filters change, receives merged preset + user filters */
|
|
1203
|
+
onChange?: (mergedFilters: Record<string, any>) => void;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/** Composable filter panel for managing search filters with URL query sync */
|
|
1207
|
+
export function FilterView(props: FilterViewProps): React.ReactElement;
|
|
1208
|
+
|
|
1209
|
+
/** Parse and validate filter parameters from the current URL query string */
|
|
1210
|
+
export function parseFiltersFromQuery(fields: FilterFieldDescriptor): Record<string, any>;
|
|
1211
|
+
|
|
1182
1212
|
// ============================================================================
|
|
1183
1213
|
// Stellar Utilities
|
|
1184
1214
|
// ============================================================================
|
|
@@ -1208,7 +1238,7 @@ export function signatureHintToMask(hint: Buffer): string;
|
|
|
1208
1238
|
export function formatSignatureHint(hint: Buffer): string;
|
|
1209
1239
|
|
|
1210
1240
|
/** Check if a hint matches a specific key (note: function name has a typo) */
|
|
1211
|
-
export function
|
|
1241
|
+
export function signatureHintMatchesKey(hint: Buffer, key: string): boolean;
|
|
1212
1242
|
|
|
1213
1243
|
/** Find a key matching a signature hint */
|
|
1214
1244
|
export function findKeyBySignatureHint(hint: Buffer, allKeys: string[]): string | null;
|
package/index.js
CHANGED
|
@@ -1,93 +1,95 @@
|
|
|
1
|
-
import './index.scss'
|
|
2
|
-
|
|
3
|
-
window.explorerFrontendOrigin = window.explorerFrontendOrigin || 'https://stellar.expert'
|
|
4
|
-
window.explorerApiOrigin = window.explorerApiOrigin || 'https://api.stellar.expert'
|
|
5
|
-
window.horizonOrigin = window.horizonOrigin || 'https://horizon.stellar.org'
|
|
6
|
-
|
|
7
|
-
//modules
|
|
8
|
-
export * from './module/dynamic-module'
|
|
9
|
-
//state management and utils
|
|
10
|
-
export * from './state/state-hooks'
|
|
11
|
-
export * from './state/on-screen-hooks'
|
|
12
|
-
export * from './state/stellar-network-hooks'
|
|
13
|
-
export * from './state/screen-orientation-hooks'
|
|
14
|
-
export * from './state/page-visibility-helpers'
|
|
15
|
-
export * from './state/theme'
|
|
16
|
-
export * from './meta/page-meta-tags'
|
|
17
|
-
//explorer API bindings
|
|
18
|
-
export * from './api/explorer-api-hooks'
|
|
19
|
-
export * from './api/explorer-api-paginated-list-hooks'
|
|
20
|
-
export * from './api/explorer-tx-api'
|
|
21
|
-
export * from './api/explorer-batch-info-loader'
|
|
22
|
-
export * from './api/ledger-stream'
|
|
23
|
-
//Horizon API binding and utils
|
|
24
|
-
export * from './stellar/ledger-generic-id'
|
|
25
|
-
export * from './horizon/horizon-client-helpers'
|
|
26
|
-
export * from './horizon/horizon-ledger-helpers'
|
|
27
|
-
export * from './horizon/horizon-transaction-helpers'
|
|
28
|
-
export * from './horizon/horizon-account-helpers'
|
|
29
|
-
export * from './horizon/horizon-orderbook-helpers'
|
|
30
|
-
export * from './horizon/horizon-trades-helper'
|
|
31
|
-
//basic UI controls
|
|
32
|
-
export * from './controls/button'
|
|
33
|
-
export * from './controls/button-group'
|
|
34
|
-
export * from './controls/info-tooltip'
|
|
35
|
-
export * from './controls/tooltip'
|
|
36
|
-
export * from './controls/update-highlighter'
|
|
37
|
-
export * from './controls/tabs'
|
|
38
|
-
export * from './controls/dropdown'
|
|
39
|
-
export * from './controls/code-block'
|
|
40
|
-
export * from './controls/slider'
|
|
41
|
-
export * from './controls/external-link'
|
|
42
|
-
export * from './toast/toast-notifications-block'
|
|
43
|
-
export * from './errors/error-boundary'
|
|
44
|
-
//interaction
|
|
45
|
-
export * from './interaction/autofocus'
|
|
46
|
-
export * from './interaction/block-select'
|
|
47
|
-
export * from './interaction/copy-to-clipboard'
|
|
48
|
-
export * from './interaction/spoiler'
|
|
49
|
-
export * from './interaction/accordion'
|
|
50
|
-
export * from './interaction/theme-selector'
|
|
51
|
-
export * from './interaction/inline-progress'
|
|
52
|
-
export * from './interaction/responsive'
|
|
53
|
-
export * from './interaction/qr-code'
|
|
54
|
-
export * from './interaction/dialog'
|
|
55
|
-
export * from './interaction/system-dialog'
|
|
56
|
-
//date components
|
|
57
|
-
export * from './date/utc-timestamp'
|
|
58
|
-
export * from './date/elapsed-time'
|
|
59
|
-
export * from './date/date-selector'
|
|
60
|
-
//ledger-entries-related components
|
|
61
|
-
export * from './ledger/ledger-entry-link'
|
|
62
|
-
export * from './ledger/ledger-entry-href-formatter'
|
|
63
|
-
export * from './ledger/ledger-info-parser'
|
|
64
|
-
//account-related components
|
|
65
|
-
export * from './account/identicon'
|
|
66
|
-
export * from './account/account-address'
|
|
67
|
-
export * from './account/signer-key'
|
|
68
|
-
export * from './account/available-balance'
|
|
69
|
-
//asset-related components
|
|
70
|
-
export * from './asset/asset-link'
|
|
71
|
-
export * from './asset/asset-issuer'
|
|
72
|
-
export * from './asset/asset-icon'
|
|
73
|
-
export * from './asset/asset-selector'
|
|
74
|
-
export * from './asset/amount'
|
|
75
|
-
export * from './asset/asset-meta-hooks'
|
|
76
|
-
export * from './asset/asset-list-hooks'
|
|
77
|
-
//claimable-balance-related components
|
|
78
|
-
export * from './claimable-balance/claimable-balance-claimants'
|
|
79
|
-
//DEX-related components
|
|
80
|
-
export * from './dex/price-dynamic'
|
|
81
|
-
//directory-related components
|
|
82
|
-
export * from './directory/directory-hooks'
|
|
83
|
-
//transaction/operation/effects components
|
|
84
|
-
export * from './tx/tx-operations-list'
|
|
85
|
-
export * from './tx/parser/tx-details-parser'
|
|
86
|
-
export * from './tx/tx-list-hooks'
|
|
87
|
-
export * from './effect/effect-description'
|
|
88
|
-
//contract-related components
|
|
89
|
-
export * from './contract/contract-api'
|
|
90
|
-
//
|
|
91
|
-
export * from './
|
|
92
|
-
|
|
93
|
-
export * from './
|
|
1
|
+
import './index.scss'
|
|
2
|
+
|
|
3
|
+
window.explorerFrontendOrigin = window.explorerFrontendOrigin || 'https://stellar.expert'
|
|
4
|
+
window.explorerApiOrigin = window.explorerApiOrigin || 'https://api.stellar.expert'
|
|
5
|
+
window.horizonOrigin = window.horizonOrigin || 'https://horizon.stellar.org'
|
|
6
|
+
|
|
7
|
+
//modules
|
|
8
|
+
export * from './module/dynamic-module'
|
|
9
|
+
//state management and utils
|
|
10
|
+
export * from './state/state-hooks'
|
|
11
|
+
export * from './state/on-screen-hooks'
|
|
12
|
+
export * from './state/stellar-network-hooks'
|
|
13
|
+
export * from './state/screen-orientation-hooks'
|
|
14
|
+
export * from './state/page-visibility-helpers'
|
|
15
|
+
export * from './state/theme'
|
|
16
|
+
export * from './meta/page-meta-tags'
|
|
17
|
+
//explorer API bindings
|
|
18
|
+
export * from './api/explorer-api-hooks'
|
|
19
|
+
export * from './api/explorer-api-paginated-list-hooks'
|
|
20
|
+
export * from './api/explorer-tx-api'
|
|
21
|
+
export * from './api/explorer-batch-info-loader'
|
|
22
|
+
export * from './api/ledger-stream'
|
|
23
|
+
//Horizon API binding and utils
|
|
24
|
+
export * from './stellar/ledger-generic-id'
|
|
25
|
+
export * from './horizon/horizon-client-helpers'
|
|
26
|
+
export * from './horizon/horizon-ledger-helpers'
|
|
27
|
+
export * from './horizon/horizon-transaction-helpers'
|
|
28
|
+
export * from './horizon/horizon-account-helpers'
|
|
29
|
+
export * from './horizon/horizon-orderbook-helpers'
|
|
30
|
+
export * from './horizon/horizon-trades-helper'
|
|
31
|
+
//basic UI controls
|
|
32
|
+
export * from './controls/button'
|
|
33
|
+
export * from './controls/button-group'
|
|
34
|
+
export * from './controls/info-tooltip'
|
|
35
|
+
export * from './controls/tooltip'
|
|
36
|
+
export * from './controls/update-highlighter'
|
|
37
|
+
export * from './controls/tabs'
|
|
38
|
+
export * from './controls/dropdown'
|
|
39
|
+
export * from './controls/code-block'
|
|
40
|
+
export * from './controls/slider'
|
|
41
|
+
export * from './controls/external-link'
|
|
42
|
+
export * from './toast/toast-notifications-block'
|
|
43
|
+
export * from './errors/error-boundary'
|
|
44
|
+
//interaction
|
|
45
|
+
export * from './interaction/autofocus'
|
|
46
|
+
export * from './interaction/block-select'
|
|
47
|
+
export * from './interaction/copy-to-clipboard'
|
|
48
|
+
export * from './interaction/spoiler'
|
|
49
|
+
export * from './interaction/accordion'
|
|
50
|
+
export * from './interaction/theme-selector'
|
|
51
|
+
export * from './interaction/inline-progress'
|
|
52
|
+
export * from './interaction/responsive'
|
|
53
|
+
export * from './interaction/qr-code'
|
|
54
|
+
export * from './interaction/dialog'
|
|
55
|
+
export * from './interaction/system-dialog'
|
|
56
|
+
//date components
|
|
57
|
+
export * from './date/utc-timestamp'
|
|
58
|
+
export * from './date/elapsed-time'
|
|
59
|
+
export * from './date/date-selector'
|
|
60
|
+
//ledger-entries-related components
|
|
61
|
+
export * from './ledger/ledger-entry-link'
|
|
62
|
+
export * from './ledger/ledger-entry-href-formatter'
|
|
63
|
+
export * from './ledger/ledger-info-parser'
|
|
64
|
+
//account-related components
|
|
65
|
+
export * from './account/identicon'
|
|
66
|
+
export * from './account/account-address'
|
|
67
|
+
export * from './account/signer-key'
|
|
68
|
+
export * from './account/available-balance'
|
|
69
|
+
//asset-related components
|
|
70
|
+
export * from './asset/asset-link'
|
|
71
|
+
export * from './asset/asset-issuer'
|
|
72
|
+
export * from './asset/asset-icon'
|
|
73
|
+
export * from './asset/asset-selector'
|
|
74
|
+
export * from './asset/amount'
|
|
75
|
+
export * from './asset/asset-meta-hooks'
|
|
76
|
+
export * from './asset/asset-list-hooks'
|
|
77
|
+
//claimable-balance-related components
|
|
78
|
+
export * from './claimable-balance/claimable-balance-claimants'
|
|
79
|
+
//DEX-related components
|
|
80
|
+
export * from './dex/price-dynamic'
|
|
81
|
+
//directory-related components
|
|
82
|
+
export * from './directory/directory-hooks'
|
|
83
|
+
//transaction/operation/effects components
|
|
84
|
+
export * from './tx/tx-operations-list'
|
|
85
|
+
export * from './tx/parser/tx-details-parser'
|
|
86
|
+
export * from './tx/tx-list-hooks'
|
|
87
|
+
export * from './effect/effect-description'
|
|
88
|
+
//contract-related components
|
|
89
|
+
export * from './contract/contract-api'
|
|
90
|
+
//filter component
|
|
91
|
+
export * from './filter/filter-view'
|
|
92
|
+
//Stellar-specific utils
|
|
93
|
+
export * from './stellar/key-type'
|
|
94
|
+
export * from './stellar/signature-hint-utils'
|
|
95
|
+
export * from './contract/sc-val'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stellar-expert/ui-framework",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
4
4
|
"description": "StellarExpert shared UI components library",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"author": "orbitlens <orbit@stellar.expert>",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@stellar/stellar-base": "^
|
|
19
|
-
"@stellar/stellar-sdk": "^
|
|
18
|
+
"@stellar/stellar-base": "^15.0.0",
|
|
19
|
+
"@stellar/stellar-sdk": "^15.0.1",
|
|
20
20
|
"@stellar-expert/asset-descriptor": "^1.5.0",
|
|
21
21
|
"@stellar-expert/claimable-balance-utils": "^1.4.1",
|
|
22
22
|
"@stellar-expert/client-cache": "^1.1.0",
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
"@stellar-expert/navigation": "^1.1.0",
|
|
26
26
|
"@stellar-expert/tx-meta-effects-parser": "^8.3.3",
|
|
27
27
|
"classnames": ">=2",
|
|
28
|
+
"deepmerge": "^4.3.1",
|
|
28
29
|
"prop-types": ">=15",
|
|
29
30
|
"qrcode.react": "^4.2.0",
|
|
30
|
-
"react": ">=17",
|
|
31
|
-
"react-dom": ">=17",
|
|
31
|
+
"react": ">=17 <18.0.0",
|
|
32
|
+
"react-dom": ">=17 <18.0.0",
|
|
32
33
|
"react-fast-compare": ">=3",
|
|
33
34
|
"throttle-debounce": ">=5.0.0"
|
|
34
35
|
},
|
|
@@ -37,5 +38,34 @@
|
|
|
37
38
|
"history": "^4.10.1",
|
|
38
39
|
"react-copy-to-clipboard": "^5.1.0",
|
|
39
40
|
"react-timeago": "^7.2.0"
|
|
40
|
-
}
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"index.js",
|
|
44
|
+
"index.d.ts",
|
|
45
|
+
"index.scss",
|
|
46
|
+
".npmrc",
|
|
47
|
+
"account/*",
|
|
48
|
+
"api/*",
|
|
49
|
+
"asset/*",
|
|
50
|
+
"basic-styles/*",
|
|
51
|
+
"claimable-balance/*",
|
|
52
|
+
"contract/*",
|
|
53
|
+
"controls/*",
|
|
54
|
+
"date/*",
|
|
55
|
+
"dex/*",
|
|
56
|
+
"directory/*",
|
|
57
|
+
"effect/*",
|
|
58
|
+
"errors/*",
|
|
59
|
+
"filter/*",
|
|
60
|
+
"fonts/*",
|
|
61
|
+
"horizon/*",
|
|
62
|
+
"interaction/*",
|
|
63
|
+
"ledger/*",
|
|
64
|
+
"meta/*",
|
|
65
|
+
"module/*",
|
|
66
|
+
"state/*",
|
|
67
|
+
"stellar/*",
|
|
68
|
+
"toast/*",
|
|
69
|
+
"tx/*"
|
|
70
|
+
]
|
|
41
71
|
}
|
|
@@ -28,7 +28,7 @@ export function formatSignatureHint(hint) {
|
|
|
28
28
|
* @param {string} key - Key to compare
|
|
29
29
|
* @return {boolean}
|
|
30
30
|
*/
|
|
31
|
-
export function
|
|
31
|
+
export function signatureHintMatchesKey(hint, key) {
|
|
32
32
|
return signatureHintToMask(hint).substring(47, 52) === key.substring(47, 52)
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -39,7 +39,7 @@ export function singatureHintMatchesKey(hint, key) {
|
|
|
39
39
|
* @return {string|null}
|
|
40
40
|
*/
|
|
41
41
|
export function findKeyBySignatureHint(hint, allKeys) {
|
|
42
|
-
return allKeys.find(key =>
|
|
42
|
+
return allKeys.find(key => signatureHintMatchesKey(hint, key))
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -49,7 +49,7 @@ export function findKeyBySignatureHint(hint, allKeys) {
|
|
|
49
49
|
* @returns {Signature}
|
|
50
50
|
*/
|
|
51
51
|
export function findSignatureByKey(key, allSignatures = []) {
|
|
52
|
-
return allSignatures.find(sig =>
|
|
52
|
+
return allSignatures.find(sig => signatureHintMatchesKey(sig.hint(), key))
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
package/CLAUDE.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
`@stellar-expert/ui-framework` is a shared React UI component library for the StellarExpert Stellar blockchain explorer. It is consumed as an npm package by other StellarExpert applications — it is not a standalone app.
|
|
8
|
-
|
|
9
|
-
## Development Setup
|
|
10
|
-
|
|
11
|
-
- **Package manager:** PNPM (configured via `.npmrc` with `auto-install-peers=true`)
|
|
12
|
-
- **Install dependencies:** `pnpm install`
|
|
13
|
-
- **No build step:** Components are raw ES modules consumed directly via imports. There is no bundler, transpiler, or build script in this repo.
|
|
14
|
-
- **No test suite:** No testing framework is configured.
|
|
15
|
-
|
|
16
|
-
## Architecture
|
|
17
|
-
|
|
18
|
-
### Module Organization
|
|
19
|
-
|
|
20
|
-
Each feature domain lives in its own top-level directory (e.g., `account/`, `asset/`, `contract/`, `controls/`). The main entry point `index.js` re-exports everything and also sets global defaults (`window.explorerFrontendOrigin`, `window.explorerApiOrigin`, `window.horizonOrigin`).
|
|
21
|
-
|
|
22
|
-
### Key Patterns
|
|
23
|
-
|
|
24
|
-
- **Functional React components** with `React.memo` for optimization and `PropTypes` for runtime type checking (no TypeScript).
|
|
25
|
-
- **Custom state hooks** instead of Redux/MobX — see `state/state-hooks.js` for `useDependantState` (auto-reinits on deep-compared dependency changes), `useForceUpdate`, and `useDeepEffect`.
|
|
26
|
-
- **SCSS styling** with CSS custom properties for theming. Global styles imported via `index.scss`. Theme system supports `day`/`night` modes via `data-theme` attribute on `<html>`. SCSS files are marked as side effects in `package.json`.
|
|
27
|
-
- **API layer** in `api/` uses custom hooks (`useExplorerApi`) with built-in caching, auto-refresh on visibility, and Stellar network awareness via `useStellarNetwork`.
|
|
28
|
-
- **Peer dependencies** are critical — the consuming app must provide React, Stellar SDK, and the `@stellar-expert/*` utility packages listed in `package.json`.
|
|
29
|
-
|
|
30
|
-
### Global Configuration
|
|
31
|
-
|
|
32
|
-
The library expects these globals (with defaults set in `index.js`):
|
|
33
|
-
- `window.explorerFrontendOrigin` — StellarExpert frontend URL
|
|
34
|
-
- `window.explorerApiOrigin` — StellarExpert API URL
|
|
35
|
-
- `window.horizonOrigin` — Stellar Horizon API URL
|