@stellar-expert/ui-framework 1.9.0

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.
Files changed (124) hide show
  1. package/.npmrc +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/account/account-address.js +127 -0
  5. package/account/account-address.scss +20 -0
  6. package/account/available-balance.js +21 -0
  7. package/account/identicon.js +91 -0
  8. package/account/identicon.scss +6 -0
  9. package/account/signer-key.js +65 -0
  10. package/api/api-cache.js +5 -0
  11. package/api/explorer-api-call.js +37 -0
  12. package/api/explorer-api-hooks.js +202 -0
  13. package/api/explorer-api-paginated-list-hooks.js +439 -0
  14. package/api/explorer-batch-info-loader.js +85 -0
  15. package/api/explorer-tx-api.js +29 -0
  16. package/asset/amount.js +53 -0
  17. package/asset/asset-icon.js +34 -0
  18. package/asset/asset-issuer.js +22 -0
  19. package/asset/asset-link.js +93 -0
  20. package/asset/asset-link.scss +26 -0
  21. package/asset/asset-list-hooks.js +60 -0
  22. package/asset/asset-meta-hooks.js +78 -0
  23. package/asset/asset-selector.js +72 -0
  24. package/asset/asset-selector.scss +41 -0
  25. package/basic-styles/base.scss +53 -0
  26. package/basic-styles/form.scss +90 -0
  27. package/basic-styles/grid.scss +388 -0
  28. package/basic-styles/link.scss +17 -0
  29. package/basic-styles/lists.scss +68 -0
  30. package/basic-styles/loader.scss +22 -0
  31. package/basic-styles/responsive.scss +29 -0
  32. package/basic-styles/scrollbars.scss +19 -0
  33. package/basic-styles/spacing.scss +50 -0
  34. package/basic-styles/table.scss +104 -0
  35. package/basic-styles/themes.scss +59 -0
  36. package/basic-styles/typography.scss +191 -0
  37. package/basic-styles/variables.scss +26 -0
  38. package/claimable-balance/claimable-balance-claimants.js +12 -0
  39. package/contract/sc-val.js +70 -0
  40. package/contract/wasm-section-parser.js +159 -0
  41. package/contract/wasm-section-reader.js +79 -0
  42. package/contract/xdr-reader.js +148 -0
  43. package/controls/active-icon.scss +23 -0
  44. package/controls/button-group.js +20 -0
  45. package/controls/button-group.scss +48 -0
  46. package/controls/button.js +74 -0
  47. package/controls/button.scss +148 -0
  48. package/controls/code-block.js +35 -0
  49. package/controls/code-block.scss +74 -0
  50. package/controls/dropdown.js +281 -0
  51. package/controls/dropdown.scss +161 -0
  52. package/controls/external-link.js +5 -0
  53. package/controls/info-tooltip.js +17 -0
  54. package/controls/info-tooltip.scss +4 -0
  55. package/controls/slider.js +20 -0
  56. package/controls/slider.scss +22 -0
  57. package/controls/tabs.js +94 -0
  58. package/controls/tabs.scss +73 -0
  59. package/controls/tooltip.js +214 -0
  60. package/controls/tooltip.scss +97 -0
  61. package/controls/update-highlighter.js +28 -0
  62. package/controls/update-highlighter.scss +10 -0
  63. package/date/date-selector.js +44 -0
  64. package/date/elapsed-time.js +22 -0
  65. package/date/utc-timestamp.js +23 -0
  66. package/dex/price-dynamic.js +45 -0
  67. package/dex/price-dynamic.scss +36 -0
  68. package/directory/directory-hooks.js +89 -0
  69. package/effect/effect-description.js +289 -0
  70. package/effect/op-effects-view.js +11 -0
  71. package/effect/op-effects.scss +8 -0
  72. package/errors/error-boundary.js +98 -0
  73. package/fonts/font.scss +23 -0
  74. package/fonts/icons/config.json +1858 -0
  75. package/fonts/icons/icons-embedded.scss +644 -0
  76. package/horizon/horizon-account-helpers.js +104 -0
  77. package/horizon/horizon-client-helpers.js +52 -0
  78. package/horizon/horizon-ledger-helpers.js +36 -0
  79. package/horizon/horizon-orderbook-helpers.js +14 -0
  80. package/horizon/horizon-trades-helper.js +89 -0
  81. package/horizon/horizon-transaction-helpers.js +36 -0
  82. package/index.js +85 -0
  83. package/index.scss +16 -0
  84. package/interaction/autofocus.js +9 -0
  85. package/interaction/block-select.js +54 -0
  86. package/interaction/block-select.scss +24 -0
  87. package/interaction/copy-to-clipboard.js +19 -0
  88. package/interaction/inline-progress.js +16 -0
  89. package/interaction/qr-code.js +45 -0
  90. package/interaction/responsive.js +21 -0
  91. package/interaction/spoiler.js +40 -0
  92. package/interaction/spoiler.scss +9 -0
  93. package/interaction/theme-selector.js +11 -0
  94. package/ledger/ledger-entry-href-formatter.js +22 -0
  95. package/ledger/ledger-entry-link.js +59 -0
  96. package/meta/page-meta-tags.js +229 -0
  97. package/module/dynamic-module.js +48 -0
  98. package/package.json +41 -0
  99. package/state/on-screen-hooks.js +23 -0
  100. package/state/page-visibility-helpers.js +17 -0
  101. package/state/page-visibility-hooks.js +12 -0
  102. package/state/screen-orientation-hooks.js +16 -0
  103. package/state/state-hooks.js +77 -0
  104. package/state/stellar-network-hooks.js +45 -0
  105. package/state/theme.js +29 -0
  106. package/stellar/key-type.js +92 -0
  107. package/stellar/ledger-generic-id.js +40 -0
  108. package/stellar/signature-hint-utils.js +65 -0
  109. package/toast/toast-notification-instance.js +43 -0
  110. package/toast/toast-notification.js +46 -0
  111. package/toast/toast-notifications-block.js +55 -0
  112. package/toast/toast-notifications.scss +166 -0
  113. package/tx/op-accounting-changes.js +27 -0
  114. package/tx/op-description-view.js +925 -0
  115. package/tx/op-description.scss +98 -0
  116. package/tx/op-icon.js +93 -0
  117. package/tx/parser/op-balance-changes.js +62 -0
  118. package/tx/parser/op-descriptor.js +49 -0
  119. package/tx/parser/tx-details-parser.js +75 -0
  120. package/tx/parser/tx-matcher.js +372 -0
  121. package/tx/parser/type-filter-matcher.js +127 -0
  122. package/tx/tx-fee-effect.js +14 -0
  123. package/tx/tx-list-hooks.js +19 -0
  124. package/tx/tx-operations-list.js +105 -0
@@ -0,0 +1,104 @@
1
+ import {applyListQueryParameters, loadAllHorizonRecords, initHorizon} from './horizon-client-helpers'
2
+
3
+ /**
4
+ * Retrieve account status from Horizon
5
+ * @param {String} accountAddress
6
+ * @returns {Promise<AccountResponse>}
7
+ */
8
+ export function loadAccount(accountAddress) {
9
+ return initHorizon()
10
+ .loadAccount(accountAddress)
11
+ }
12
+
13
+ /**
14
+ * Load assets issued by the account from Horizon
15
+ * @param {String} account - Account address
16
+ * @param {ListQueryParams} [queryParams] - Query parameters (optional)
17
+ * @return {Promise<Array<Object>>}
18
+ */
19
+ export function loadIssuedAssets(account, queryParams = null) {
20
+ const query = applyListQueryParameters(initHorizon().assets().forIssuer(account).limit(200), queryParams)
21
+ return query.call()
22
+ .then(({records}) => {
23
+ if (records && records.length) {
24
+ records.sort((a, b) => a.asset_code - b.asset_code)
25
+ }
26
+ return records || []
27
+ })
28
+ }
29
+
30
+ /**
31
+ * Load offer from Horizon for the particular account
32
+ * @param {String} account - Account address
33
+ * @param {ListQueryParams} [queryParams] - Query parameters (optional)
34
+ * @return {Promise<Array<Object>>}
35
+ */
36
+ export function loadAccountOffers(account, queryParams = null) {
37
+ const query = applyListQueryParameters(initHorizon().offers().forAccount(account), queryParams)
38
+ return loadAllHorizonRecords(query)
39
+ .then(r => r.records)
40
+ }
41
+
42
+
43
+ /**
44
+ * Load pending claimable balances for a given account pubkey
45
+ * @param {String} account - Account address
46
+ * @return {Promise<Array<Object>>}
47
+ */
48
+ export function loadAccountClaimableBalances(account) {
49
+ return initHorizon().claimableBalances()
50
+ .claimant(account)
51
+ .limit(100)
52
+ .call()
53
+ .then(r => r.records)
54
+ }
55
+
56
+ /**
57
+ * Detect account lock status based on the signers configuration
58
+ * @param {AccountResponse} account - Account info from Horizon
59
+ * @returns {'unlocked'|'locked'|'partially locked'}
60
+ */
61
+ export function getAccountLockStatus(account) {
62
+ const {high_threshold, med_threshold, low_threshold} = account.thresholds
63
+
64
+ //calculate the sum of all signers weights
65
+ const totalSignersWeight = account.signers.reduce((res, signer) => res + signer.weight, 0)
66
+
67
+ if (totalSignersWeight > 0) {
68
+ //compare it with med_threshold and high_threshold
69
+ if (totalSignersWeight >= med_threshold || totalSignersWeight >= high_threshold) {
70
+ return 'unlocked'
71
+ } else if (totalSignersWeight >= low_threshold) {
72
+ return 'partially locked'
73
+ }
74
+ }
75
+ return 'locked'
76
+ }
77
+
78
+ /**
79
+ * Retrieve balance for a given Horizon account info
80
+ * @param {AccountResponse} account - Account Horizon info
81
+ * @param {{code: String, issuer: String}} asset - Asset or null (for XLM)
82
+ * @returns {{total: Number, available: Number, [asset]: {code: String, issuer: String}}}
83
+ */
84
+ export function getAccountBalance(account, asset = null) {
85
+ if (!account.balances) return {asset, total: 0, available: 0}
86
+ const assetBalance = account.balances.find(b => (!asset && b.asset_type === 'native') ||
87
+ (b.asset_issuer === asset.issuer && b.asset_code === asset.code)),
88
+ res = {asset, total: 0, available: 0}
89
+
90
+ if (assetBalance) {
91
+ res.total = parseFloat(assetBalance.balance)
92
+ res.available = res.total - parseFloat(assetBalance.selling_liabilities)
93
+ if (!asset) { //for XLM we also need to check reserved amount
94
+ res.available -= (parseInt(account.subentry_count) + 2) * baseReserve
95
+ }
96
+ } else if (asset && asset.issuer === account.address) {
97
+ res.total = res.available = 922337203685
98
+ }
99
+
100
+ return res
101
+ }
102
+
103
+
104
+
@@ -0,0 +1,52 @@
1
+ import {Horizon} from '@stellar/stellar-sdk'
2
+
3
+ export function initHorizon() {
4
+ return new Horizon.Server(window.horizonOrigin)
5
+ }
6
+
7
+ /**
8
+ * Load all data from the horizon query using next() iteration
9
+ * @param {CallBuilder} query
10
+ * @return {Promise<Object[]>}
11
+ */
12
+ export async function loadAllHorizonRecords(query) {
13
+ let res = []
14
+ let response = await query.call()
15
+ while (true) {
16
+ const {records = []} = response
17
+ const limitParam = (query.url.query() || '')
18
+ .split('&')
19
+ .find(q => q.indexOf('limit=') === 0)
20
+ const limit = (limitParam && parseInt(limitParam.split('=')[1])) || 10
21
+ if (!records.length) break
22
+ res = res.concat(records)
23
+ if (records.length < limit) break
24
+ response = await response.next()
25
+ }
26
+ return {records: res}
27
+ }
28
+
29
+ /**
30
+ * @typedef {Object} ListQueryParams
31
+ * @property {string} [cursor] - Paging cursor.
32
+ * @property [('asc'|'desc'))] [order] - Sort order.
33
+ * @property {number} [limit] - Page size (200 by default).
34
+ */
35
+
36
+ /**
37
+ * Apply list query parameters to horizon query
38
+ * @param {object} query - Horizon query.
39
+ * @param {object} queryParameters - Parameters to apply.
40
+ * @return {object}
41
+ */
42
+ export function applyListQueryParameters(query, queryParameters) {
43
+ const {cursor, order, limit = 200} = queryParameters || {}
44
+ if (cursor) {
45
+ query.cursor(cursor)
46
+ }
47
+ if (order) {
48
+ query.order(order || 'asc')
49
+ }
50
+ query.limit(limit)
51
+ return query
52
+ }
@@ -0,0 +1,36 @@
1
+ import {initHorizon, applyListQueryParameters} from './horizon-client-helpers'
2
+
3
+ /**
4
+ * Load ledgers from Horizon
5
+ * @param {ListQueryParams} [queryParams] - Query parameters (optional)
6
+ * @return {Promise<Array<Object>>}
7
+ */
8
+ export function loadLedgers(queryParams = null) {
9
+ const query = applyListQueryParameters(initHorizon().ledgers(), queryParams)
10
+ return query.call()
11
+ .then(r => r.records)
12
+ }
13
+
14
+ /**
15
+ * Load ledger by its sequence
16
+ * @param {Number} sequence - Sequence of the ledger to fetch
17
+ * @return {Object}
18
+ */
19
+ export function loadLedger(sequence) {
20
+ return initHorizon().ledgers()
21
+ .ledger(sequence)
22
+ .call()
23
+ }
24
+
25
+ /**
26
+ * Stream ledgers from Horizon
27
+ * @param {String} cursor - Cursor to start from
28
+ * @param {Function} onNewLedger - Callback to invoke when new ledger arrives
29
+ * @return {Function}
30
+ */
31
+ export function streamLedgers(cursor, onNewLedger) {
32
+ return initHorizon().ledgers()
33
+ .order('asc')
34
+ .cursor(cursor || 'now')
35
+ .stream({onmessage: ledger => onNewLedger(ledger)})
36
+ }
@@ -0,0 +1,14 @@
1
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
2
+ import {applyListQueryParameters, initHorizon} from './horizon-client-helpers'
3
+
4
+ /**
5
+ * Load orderbook for assets.
6
+ * @param {object} selling - Selling asset.
7
+ * @param {object} buying - Buying asset.
8
+ * @param {ListQueryParams} [queryParams] - Query parameters (optional).
9
+ * @return {Promise<Array<object>>}
10
+ */
11
+ export function loadOrderbook(selling, buying, queryParams = null) {
12
+ const query = applyListQueryParameters(initHorizon().orderbook(AssetDescriptor.parse(selling).toAsset(), AssetDescriptor.parse(buying).toAsset()), queryParams)
13
+ return query.call()
14
+ }
@@ -0,0 +1,89 @@
1
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
2
+ import {initHorizon, applyListQueryParameters} from './horizon-client-helpers'
3
+
4
+ /**
5
+ * Load offer from Horizon for the particular account
6
+ * @param {AssetDescriptor} baseAsset - Base asset descriptor
7
+ * @param {AssetDescriptor} counterAsset - Counter asset descriptor
8
+ * @param {ListQueryParams} [queryParams] - Query parameters (optional)
9
+ * @return {Promise<Array<Object>>}
10
+ */
11
+ export function loadMarketTrades(baseAsset, counterAsset, queryParams = null) {
12
+ const q = initHorizon().trades()
13
+ .forAssetPair(baseAsset.toAsset(), counterAsset.toAsset())
14
+ const query = applyListQueryParameters(q, queryParams)
15
+ return query.call()
16
+ .then(r=>r.records)
17
+ }
18
+
19
+
20
+ /**
21
+ * Stream trades from Horizon
22
+ * @param {String} cursor - Cursor to start from
23
+ * @param {AssetDescriptor} baseAsset - Base asset descriptor
24
+ * @param {AssetDescriptor} counterAsset - Counter asset descriptor
25
+ * @param {Function} onNewTrade - Callback to invoke when new trade arrives
26
+ * @return {Function}
27
+ */
28
+ export function streamMarketTrades(cursor, baseAsset, counterAsset, onNewTrade) {
29
+ return initHorizon().trades()
30
+ .forAssetPair(baseAsset.toAsset(), counterAsset.toAsset())
31
+ .order('asc')
32
+ .cursor(cursor || 'now')
33
+ .stream({onmessage: op => onNewTrade(op)})
34
+ }
35
+
36
+
37
+ /**
38
+ * Stream all trades from Horizon
39
+ * @param {String} cursor - Cursor to start from
40
+ * @param {Function} onNewTrade - Callback to invoke when new trade arrives
41
+ * @return {Function}
42
+ */
43
+ export function streamTrades(cursor, onNewTrade) {
44
+ return initHorizon().trades()
45
+ .order('asc')
46
+ .cursor(cursor || 'now')
47
+ .limit(200)
48
+ .stream({onmessage: op => onNewTrade(op)})
49
+ }
50
+
51
+
52
+ /**
53
+ *
54
+ * @param {String|AssetDescriptor|Asset} base - Base asset
55
+ * @param {String|AssetDescriptor|Asset} counter - Counter asset
56
+ * @param {('5m'|'15m'|'1h'|'1d'|'1w')} resolution - Segment duration
57
+ * @param {Number} period - Period in days
58
+ * @param {Number} [limit] - Max number of records to fetch
59
+ * @return {Promise<TradeAggregationRecord>}
60
+ */
61
+ export function loadTradesAggregation({base, counter, resolution, period, limit = 200}) {
62
+ let res
63
+ switch (resolution) {
64
+ case '5m':
65
+ res = 300000
66
+ break
67
+ case '15m':
68
+ res = 900000
69
+ break
70
+ case '1h':
71
+ res = 3600000
72
+ break
73
+ case '1d':
74
+ res = 86400000
75
+ break
76
+ case '1w':
77
+ res = 604800000
78
+ break
79
+ default:
80
+ throw new Error(`Not supported trades aggregation resolution: ${resolution}`)
81
+ }
82
+ const endTime = new Date().getTime(),
83
+ startTime = endTime - period * 24 * 60 * 60 * 1000
84
+
85
+ return initHorizon()
86
+ .tradeAggregation(AssetDescriptor.parse(base).toAsset(), AssetDescriptor.parse(counter).toAsset(), startTime, endTime, res, 0)
87
+ .limit(limit)
88
+ .call()
89
+ }
@@ -0,0 +1,36 @@
1
+ import {initHorizon, applyListQueryParameters} from './horizon-client-helpers'
2
+
3
+ /**
4
+ * Load transactions from Horizon
5
+ * @param {ListQueryParams} [queryParams] - Query parameters (optional)
6
+ * @return {Promise<Array<Object>>}
7
+ */
8
+ export function loadTransactions(queryParams = null) {
9
+ const query = applyListQueryParameters(initHorizon().transactions().includeFailed(queryParams && !!queryParams.includeFailed), queryParams)
10
+ return query.call()
11
+ .then(r => r.records)
12
+ }
13
+
14
+ /**
15
+ * Stream operations from Horizon
16
+ * @param {String} cursor - Cursor to start from
17
+ * @param {Function} onNewTx - Callback to invoke when new operation arrives
18
+ * @param {Boolean} includeFailed - Include failed transactions into the stream
19
+ * @return {Function}
20
+ */
21
+ export function streamTransactions(cursor, onNewTx, includeFailed) {
22
+ return initHorizon().transactions().includeFailed(!!includeFailed)
23
+ .order('asc')
24
+ .cursor(cursor || 'now')
25
+ .stream({onmessage: op => onNewTx(op)})
26
+ }
27
+
28
+ /**
29
+ * Submit transaction to Horizon
30
+ * @param {Transaction} tx
31
+ * @returns {Promise<Horizon.SubmitTransactionResponse>}
32
+ */
33
+ export function submitTransaction(tx) {
34
+ return initHorizon()
35
+ .submitTransaction(tx)
36
+ }
package/index.js ADDED
@@ -0,0 +1,85 @@
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
+ //Horizon API binding and utils
22
+ export * from './stellar/ledger-generic-id'
23
+ export * from './horizon/horizon-client-helpers'
24
+ export * from './horizon/horizon-ledger-helpers'
25
+ export * from './horizon/horizon-transaction-helpers'
26
+ export * from './horizon/horizon-account-helpers'
27
+ export * from './horizon/horizon-orderbook-helpers'
28
+ export * from './horizon/horizon-trades-helper'
29
+ //basic UI controls
30
+ export * from './controls/button'
31
+ export * from './controls/button-group'
32
+ export * from './controls/info-tooltip'
33
+ export * from './controls/tooltip'
34
+ export * from './controls/update-highlighter'
35
+ export * from './controls/tabs'
36
+ export * from './controls/dropdown'
37
+ export * from './controls/code-block'
38
+ export * from './controls/slider'
39
+ export * from './controls/external-link'
40
+ export * from './toast/toast-notifications-block'
41
+ export * from './errors/error-boundary'
42
+ //interaction
43
+ export * from './interaction/autofocus'
44
+ export * from './interaction/block-select'
45
+ export * from './interaction/copy-to-clipboard'
46
+ export * from './interaction/spoiler'
47
+ export * from './interaction/theme-selector'
48
+ export * from './interaction/inline-progress'
49
+ export * from './interaction/responsive'
50
+ export * from './interaction/qr-code'
51
+ //date components
52
+ export * from './date/utc-timestamp'
53
+ export * from './date/elapsed-time'
54
+ export * from './date/date-selector'
55
+ //ledger-entries-related components
56
+ export * from './ledger/ledger-entry-link'
57
+ export * from './ledger/ledger-entry-href-formatter'
58
+ //account-related components
59
+ export * from './account/identicon'
60
+ export * from './account/account-address'
61
+ export * from './account/signer-key'
62
+ export * from './account/available-balance'
63
+ //asset-related components
64
+ export * from './asset/asset-link'
65
+ export * from './asset/asset-issuer'
66
+ export * from './asset/asset-icon'
67
+ export * from './asset/asset-selector'
68
+ export * from './asset/amount'
69
+ export * from './asset/asset-meta-hooks'
70
+ export * from './asset/asset-list-hooks'
71
+ //claimable-balance-related components
72
+ export * from './claimable-balance/claimable-balance-claimants'
73
+ //DEX-related components
74
+ export * from './dex/price-dynamic'
75
+ //directory-related components
76
+ export * from './directory/directory-hooks'
77
+ //transaction/operation/effects components
78
+ export * from './tx/tx-operations-list'
79
+ export * from './tx/parser/tx-details-parser'
80
+ export * from './tx/tx-list-hooks'
81
+ export * from './effect/effect-description'
82
+ //Stellar-specific utils
83
+ export * from './stellar/key-type'
84
+ export * from './stellar/signature-hint-utils'
85
+ export * from './contract/wasm-section-parser'
package/index.scss ADDED
@@ -0,0 +1,16 @@
1
+ @import "basic-styles/variables";
2
+ @import "basic-styles/themes";
3
+ @import "basic-styles/scrollbars";
4
+ @import "basic-styles/base";
5
+ @import "fonts/font";
6
+ @import "fonts/icons/icons-embedded";
7
+ @import "controls/active-icon";
8
+ @import "basic-styles/link";
9
+ @import "basic-styles/grid";
10
+ @import "basic-styles/loader";
11
+ @import "basic-styles/typography";
12
+ @import "basic-styles/spacing";
13
+ @import "basic-styles/form";
14
+ @import "basic-styles/lists";
15
+ @import "basic-styles/table";
16
+ @import "basic-styles/responsive";
@@ -0,0 +1,9 @@
1
+ export function useAutoFocusRef(inputRef) {
2
+ if (!inputRef)
3
+ return
4
+ setTimeout(() => {
5
+ if (document.body.contains(inputRef)) {
6
+ inputRef.focus()
7
+ }
8
+ }, 200)
9
+ }
@@ -0,0 +1,54 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import cn from 'classnames'
4
+ import './block-select.scss'
5
+
6
+ function select(target) {
7
+ //handle textarea and inputs
8
+ if (target.nodeName.match(/^(INPUT|TEXTAREA)$/i)) {
9
+ target.focus()
10
+ target.select()
11
+ return
12
+ }
13
+ //handle selection inside text elements
14
+ const range = document.createRange()
15
+ range.selectNodeContents(target)
16
+ const sel = window.getSelection()
17
+ if (typeof sel.setBaseAndExtent === 'function') {
18
+ // Safari
19
+ sel.setBaseAndExtent(target, 0, target, 1)
20
+ }
21
+ sel.removeAllRanges()
22
+ sel.addRange(range)
23
+ }
24
+
25
+ export const BlockSelect = React.memo(function BlockSelect({as = 'span', children, title, className, wrap, inline, style, ...op}) {
26
+ const props = {
27
+ className: cn('block-select', className),
28
+ onFocus: e => select(e.target),
29
+ tabIndex: '-1',
30
+ style,
31
+ title,
32
+ ...op
33
+ }
34
+ if (wrap) {
35
+ props.style = {...props.style, whiteSpace: 'normal', overflow: 'visible'}
36
+ }
37
+ if (wrap === false) {
38
+ props.style = {...props.style, whiteSpace: 'nowrap', overflow: 'hidden'}
39
+ }
40
+ if (inline) {
41
+ props.style = {...props.style, display: 'inline'}
42
+ }
43
+ return React.createElement(as, props, children)
44
+ })
45
+
46
+ BlockSelect.propTypes = {
47
+ children: PropTypes.any.isRequired,
48
+ title: PropTypes.string,
49
+ className: PropTypes.string,
50
+ style: PropTypes.object,
51
+ wrap: PropTypes.bool,
52
+ inline: PropTypes.bool,
53
+ as: PropTypes.string
54
+ }
@@ -0,0 +1,24 @@
1
+ @use "../basic-styles/variables";
2
+
3
+ .block-select {
4
+ display: inline-block;
5
+ width: auto;
6
+ overflow: auto;
7
+ text-overflow: ellipsis;
8
+ vertical-align: bottom;
9
+
10
+ &:focus {
11
+ outline: none;
12
+ }
13
+
14
+ &textarea {
15
+ width: 100%;
16
+ height: 7em;
17
+ margin: 0;
18
+ resize: vertical;
19
+
20
+ &.code {
21
+ font-family: $font-face-mono;
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import {CopyToClipboard as Copy} from 'react-copy-to-clipboard'
4
+
5
+ export const CopyToClipboard = React.memo(function CopyToClipboard({text, children, title}) {
6
+ return <Copy text={text}>
7
+ {children ? children : <a href="#" className="icon-copy active-icon" title={title}/>}
8
+ </Copy>
9
+ })
10
+
11
+ CopyToClipboard.defaultProps = {
12
+ title: 'Copy to clipboard'
13
+ }
14
+
15
+ CopyToClipboard.propTypes = {
16
+ text: PropTypes.string.isRequired,
17
+ title: PropTypes.string,
18
+ children: PropTypes.any
19
+ }
@@ -0,0 +1,16 @@
1
+ import React, {useEffect, useRef, useState} from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ export const InlineProgress = React.memo(function InlineProgress({dots = 5}) {
5
+ const [progress, setProgress] = useState(0),
6
+ intervalRef = useRef(0)
7
+ useEffect(() => {
8
+ intervalRef.current = setInterval(() => setProgress(p => ++p), 500)
9
+ return () => clearInterval(intervalRef.current)
10
+ }, [])
11
+ return <span>{'.'.repeat(progress % (dots + 1))}</span>
12
+ })
13
+
14
+ InlineProgress.propTypes = {
15
+ dots: PropTypes.number
16
+ }
@@ -0,0 +1,45 @@
1
+ import React, {useMemo, useRef} from 'react'
2
+ import QR from 'qrcode.react'
3
+
4
+ /**
5
+ * QrCode renderer
6
+ * @param {String} value - Value to encode
7
+ * @param {String} [caption] - Additional caption under QR code
8
+ * @param {Number} [size] - Width|height fo the rendered QR code image
9
+ * @param {String} [embeddedImage] - Optional logo to render in the center of QR code
10
+ * @param {Number} [embeddedSize] - Embeded logo size (by default 10% of QR code size)
11
+ * @return {JSX.Element}
12
+ */
13
+ export const QrCode = React.memo(function QrCode({value, caption, size = 320, embeddedImage, embeddedSize}) {
14
+ const foreground = useMemo(() => getComputedStyle(document.documentElement).getPropertyValue('--color-primary'))
15
+ const containerRef = useRef()
16
+ return <div className="text-center" ref={containerRef}>
17
+ <QR value={value} size={256} level="Q" includeMargin imageSettings={embedImage(embeddedImage, embeddedSize, size)}
18
+ fgColor={foreground} style={{width: size + 'px', height: size + 'px', display: 'block', margin: 'auto'}}/>
19
+ {!!caption && <div className="text-small dimmed condensed word-break">{caption}</div>}
20
+ </div>
21
+ })
22
+
23
+ function embedImage(src, size, qrSize) {
24
+ if (!src)
25
+ return undefined
26
+ if (!size) {
27
+ size = qrSize * 0.1
28
+ }
29
+ return {
30
+ src,
31
+ height: size,
32
+ width: size,
33
+ excavate: true
34
+ }
35
+ }
36
+
37
+ function download(container, caption) {
38
+ const canvas = container.querySelector('canvas')
39
+ const link = document.createElement('a')
40
+ link.href = canvas.toDataURL()
41
+ link.download = caption ? caption.replace(/\W+/g, '-') + '-qr.png' : `qr${new Date().getTime()}.png`
42
+ document.body.appendChild(link)
43
+ link.click()
44
+ document.body.removeChild(link)
45
+ }
@@ -0,0 +1,21 @@
1
+ import {useState, useEffect} from 'react'
2
+ import {throttle} from 'throttle-debounce'
3
+
4
+ /**
5
+ * Measures window client width
6
+ * @return {Number}
7
+ */
8
+ export function useWindowWidth() {
9
+ const [width, setWidth] = useState(window.innerWidth)
10
+
11
+ const onResize = throttle(100, function () {
12
+ setWidth(window.innerWidth)
13
+ })
14
+
15
+ useEffect(() => {
16
+ window.addEventListener('resize', onResize)
17
+ return () => window.removeEventListener('resize', onResize)
18
+ }, [])
19
+
20
+ return width
21
+ }
@@ -0,0 +1,40 @@
1
+ import React, {useCallback, useEffect, useState} from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import cn from 'classnames'
4
+ import './spoiler.scss'
5
+
6
+ export const Spoiler = React.memo(function Spoiler({expanded, showMore = 'Show more', showLess = 'Show less', onChange, className, micro, style, active, children}) {
7
+ const [expandedState, setExpandedState] = useState(expanded || false)
8
+ useEffect(() => {
9
+ setExpandedState(expanded)
10
+ }, [expanded])
11
+
12
+ const toggle = useCallback(() => {
13
+ setExpandedState(prevState => {
14
+ const newState = !prevState
15
+ setExpandedState(newState)
16
+ onChange && onChange({expanded: newState})
17
+ })
18
+ }, [onChange])
19
+
20
+ const text = expandedState ? showLess : showMore
21
+ return <>
22
+ <span className={cn('spoiler', className)}>
23
+ <a href="#" className={!active ? 'dimmed' : undefined} title={micro ? text : undefined} style={style} onClick={toggle}>
24
+ {!micro && <span className="spoiler-text">{text}</span>}
25
+ <i className={`icon ${expandedState ? 'icon-less' : 'icon-more'}`}/>
26
+ </a>
27
+ </span>
28
+ {expandedState ? children : null}
29
+ </>
30
+ })
31
+
32
+ Spoiler.propTypes = {
33
+ expanded: PropTypes.bool,
34
+ micro: PropTypes.bool,
35
+ onChange: PropTypes.func,
36
+ showMore: PropTypes.string,
37
+ showLess: PropTypes.string,
38
+ active: PropTypes.bool,
39
+ style: PropTypes.object
40
+ }