@visualizevalue/mint-app-base 0.1.44 → 0.1.47
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/components/Connect.client.vue +6 -1
- package/components/Token/MintTimeline.client.vue +58 -42
- package/components/Token/MintTimelineItem.vue +5 -3
- package/components/Token/MintTimelineVirtualScroller.client.vue +44 -0
- package/composables/base.ts +7 -0
- package/composables/collections.ts +3 -3
- package/package.json +3 -3
- package/pages/[id]/create.vue +4 -1
- package/utils/time.ts +6 -8
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
@click="() => login(connector)"
|
|
19
19
|
class="choose-connector-button"
|
|
20
20
|
>
|
|
21
|
-
<img
|
|
21
|
+
<img
|
|
22
|
+
v-if="ICONS[connector.name]"
|
|
23
|
+
:src="connector.icon || `${base}icons/wallets/${ICONS[connector.name]}`"
|
|
24
|
+
:alt="connector.name"
|
|
25
|
+
>
|
|
22
26
|
{{ connector.name }}
|
|
23
27
|
</Button>
|
|
24
28
|
</div>
|
|
@@ -38,6 +42,7 @@ const ICONS = {
|
|
|
38
42
|
|
|
39
43
|
const props = defineProps(['class'])
|
|
40
44
|
const emit = defineEmits(['connected', 'disconnected'])
|
|
45
|
+
const base = useBaseURL()
|
|
41
46
|
|
|
42
47
|
const chainId = useChainId()
|
|
43
48
|
const { connectors, connect } = useConnect()
|
|
@@ -3,34 +3,35 @@
|
|
|
3
3
|
<slot :mints="mints" :loading="loading">
|
|
4
4
|
<h1>Mint Timeline</h1>
|
|
5
5
|
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</
|
|
25
|
-
</
|
|
26
|
-
</
|
|
27
|
-
</
|
|
6
|
+
<template v-if="currentBlock">
|
|
7
|
+
<TokenMintTimelineVirtualScroller
|
|
8
|
+
:mints="mints"
|
|
9
|
+
:block="currentBlock"
|
|
10
|
+
>
|
|
11
|
+
<template #after>
|
|
12
|
+
<TokenMintTimelineItem v-if="backfillComplete">
|
|
13
|
+
<Account :address="collection.owner" class="account" />
|
|
14
|
+
|
|
15
|
+
<span class="amount">1<span>×</span></span>
|
|
16
|
+
<span class="price">Artist Mint</span>
|
|
17
|
+
|
|
18
|
+
<span class="time-ago"><BlocksTimeAgo v-if="currentBlock" :blocks="currentBlock - mintedAtBlock" /></span>
|
|
19
|
+
|
|
20
|
+
<span class="links">
|
|
21
|
+
<NuxtLink :to="`${config.public.blockExplorer}/nft/${token.collection}/${token.tokenId}`" target="_blank">
|
|
22
|
+
<Icon type="link" />
|
|
23
|
+
</NuxtLink>
|
|
24
|
+
</span>
|
|
25
|
+
</TokenMintTimelineItem>
|
|
26
|
+
</template>
|
|
27
|
+
</TokenMintTimelineVirtualScroller>
|
|
28
|
+
</template>
|
|
28
29
|
|
|
29
30
|
<div v-if="! backfillComplete" v-show="! loading" ref="loadMore" class="load-more">
|
|
30
31
|
<Button @click="backfill">Load more</Button>
|
|
31
32
|
</div>
|
|
32
33
|
|
|
33
|
-
<Loading v-if="loading || ! currentBlock" txt="Mint History..." />
|
|
34
|
+
<Loading v-if="loading || ! currentBlock" txt="Loading Mint History..." />
|
|
34
35
|
</slot>
|
|
35
36
|
</section>
|
|
36
37
|
</template>
|
|
@@ -54,36 +55,50 @@ const mintedAtBlock = computed(() => token.untilBlock - MINT_BLOCKS)
|
|
|
54
55
|
const backfillComplete = computed(() => token.mintsBackfilledUntilBlock <= mintedAtBlock.value)
|
|
55
56
|
|
|
56
57
|
const loading = ref(true)
|
|
58
|
+
const loadMore = ref()
|
|
59
|
+
const loadMoreVisible = useElementVisibility(loadMore)
|
|
60
|
+
const backfill = async () => {
|
|
61
|
+
loading.value = true
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await state.backfillTokenMints(token)
|
|
65
|
+
|
|
66
|
+
// If we're not fully backfilled and we have less than 20 mints loaded,
|
|
67
|
+
// continue backfilling events.
|
|
68
|
+
while (! backfillComplete.value && mints.value?.length < 20) {
|
|
69
|
+
await delay(250)
|
|
70
|
+
await state.backfillTokenMints(token)
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error(`Issue during backfill`, e)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
loading.value = false
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
onMounted(async () => {
|
|
58
80
|
loading.value = true
|
|
59
81
|
try {
|
|
60
82
|
console.info(`Attempting to load + backfill token mints for #${token.tokenId}`)
|
|
61
83
|
await state.fetchTokenMints(token)
|
|
62
|
-
await
|
|
84
|
+
await backfill()
|
|
63
85
|
} catch (e) {
|
|
64
86
|
console.error(e)
|
|
65
87
|
}
|
|
66
88
|
loading.value = false
|
|
67
89
|
})
|
|
68
90
|
|
|
69
|
-
watch(
|
|
70
|
-
if
|
|
91
|
+
watch(loadMoreVisible, () => {
|
|
92
|
+
// Skip if we have enough mints for the viewport or we're already loading
|
|
93
|
+
if (! loadMoreVisible.value || loading.value) return
|
|
71
94
|
|
|
72
|
-
|
|
95
|
+
backfill()
|
|
73
96
|
})
|
|
74
97
|
|
|
75
|
-
|
|
76
|
-
loading.value
|
|
77
|
-
await state.backfillTokenMints(token)
|
|
78
|
-
loading.value = false
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const loadMore = ref()
|
|
82
|
-
const loadMoreVisible = useElementVisibility(loadMore)
|
|
83
|
-
watch(loadMoreVisible, () => {
|
|
84
|
-
if (! loadMoreVisible.value) return
|
|
98
|
+
watch(currentBlock, () => {
|
|
99
|
+
if (loading.value) return
|
|
85
100
|
|
|
86
|
-
|
|
101
|
+
state.fetchTokenMints(token)
|
|
87
102
|
})
|
|
88
103
|
</script>
|
|
89
104
|
|
|
@@ -92,6 +107,11 @@ watch(loadMoreVisible, () => {
|
|
|
92
107
|
padding-top: var(--spacer-lg);
|
|
93
108
|
padding-bottom: var(--spacer-lg);
|
|
94
109
|
container-type: inline-size;
|
|
110
|
+
|
|
111
|
+
:deep(.token-mint-timeline-items) {
|
|
112
|
+
display: grid;
|
|
113
|
+
gap: var(--spacer);
|
|
114
|
+
}
|
|
95
115
|
}
|
|
96
116
|
|
|
97
117
|
h1 {
|
|
@@ -102,10 +122,6 @@ h1 {
|
|
|
102
122
|
margin: 0 0 var(--spacer);
|
|
103
123
|
}
|
|
104
124
|
|
|
105
|
-
.token-mint-timeline-items {
|
|
106
|
-
display: grid;
|
|
107
|
-
gap: var(--spacer);
|
|
108
|
-
}
|
|
109
125
|
|
|
110
126
|
.load-more {
|
|
111
127
|
.button {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="token-mint-timeline-item">
|
|
3
3
|
<slot :mint="mint" :formatted-price="formattedPrice">
|
|
4
|
-
<NuxtLink :to="{ name: 'profile-address', params: { address: mint.address } }">
|
|
5
|
-
<Account :address="mint.address"
|
|
4
|
+
<NuxtLink :to="{ name: 'profile-address', params: { address: mint.address } }" class="account">
|
|
5
|
+
<Account :address="mint.address" />
|
|
6
6
|
</NuxtLink>
|
|
7
7
|
|
|
8
8
|
<span class="amount">{{ mint.amount.toString() }}<span>×</span></span>
|
|
@@ -49,7 +49,7 @@ const formattedPrice = computed(() => props.mint && customFormatEther(props.mint
|
|
|
49
49
|
span {
|
|
50
50
|
white-space: nowrap;
|
|
51
51
|
|
|
52
|
-
&:not(.account) {
|
|
52
|
+
&:not(.account):not(.account *) {
|
|
53
53
|
color: var(--muted);
|
|
54
54
|
font-size: var(--font-sm);
|
|
55
55
|
}
|
|
@@ -57,6 +57,8 @@ const formattedPrice = computed(() => props.mint && customFormatEther(props.mint
|
|
|
57
57
|
|
|
58
58
|
a,
|
|
59
59
|
button {
|
|
60
|
+
color: var(--color);
|
|
61
|
+
|
|
60
62
|
&:--highlight {
|
|
61
63
|
color: var(--color);
|
|
62
64
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="wrapper">
|
|
3
|
+
<RecycleScroller
|
|
4
|
+
:items="mints"
|
|
5
|
+
:item-size="itemSize"
|
|
6
|
+
key-field="tx"
|
|
7
|
+
list-class="token-mint-timeline-items"
|
|
8
|
+
page-mode
|
|
9
|
+
>
|
|
10
|
+
<template #before>
|
|
11
|
+
<slot name="before" />
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<template #default="{ item: mint }">
|
|
15
|
+
<TokenMintTimelineItem
|
|
16
|
+
:mint="mint"
|
|
17
|
+
:key="mint.tx"
|
|
18
|
+
:block="block"
|
|
19
|
+
/>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<template #after>
|
|
23
|
+
<slot name="after" />
|
|
24
|
+
</template>
|
|
25
|
+
</RecycleScroller>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup>
|
|
30
|
+
import { useElementSize } from '@vueuse/core'
|
|
31
|
+
import { RecycleScroller } from 'vue-virtual-scroller'
|
|
32
|
+
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
|
33
|
+
|
|
34
|
+
defineProps({
|
|
35
|
+
mints: Array,
|
|
36
|
+
block: BigInt,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const wrapper = ref()
|
|
40
|
+
const REM = 16
|
|
41
|
+
const { width: wrapperWidth } = useElementSize(wrapper)
|
|
42
|
+
const itemSize = computed(() => 24 * REM > wrapperWidth.value ? 60 : 40)
|
|
43
|
+
</script>
|
|
44
|
+
|
|
@@ -4,7 +4,7 @@ import { parseAbiItem, type PublicClient } from 'viem'
|
|
|
4
4
|
import type { MintEvent } from '~/utils/types'
|
|
5
5
|
|
|
6
6
|
export const CURRENT_STATE_VERSION = 4
|
|
7
|
-
export const MAX_BLOCK_RANGE =
|
|
7
|
+
export const MAX_BLOCK_RANGE = 1800n
|
|
8
8
|
export const MINT_BLOCKS = BLOCKS_PER_DAY
|
|
9
9
|
|
|
10
10
|
export const useOnchainStore = () => {
|
|
@@ -320,7 +320,7 @@ export const useOnchainStore = () => {
|
|
|
320
320
|
},
|
|
321
321
|
|
|
322
322
|
async fetchTokenMints (token: Token) {
|
|
323
|
-
const client = getPublicClient($wagmi) as PublicClient
|
|
323
|
+
const client = getPublicClient($wagmi, { chainId }) as PublicClient
|
|
324
324
|
const currentBlock = await client.getBlockNumber()
|
|
325
325
|
const mintedAtBlock = token.untilBlock - MINT_BLOCKS
|
|
326
326
|
const storedToken = this.collections[token.collection].tokens[token.tokenId.toString()]
|
|
@@ -365,7 +365,7 @@ export const useOnchainStore = () => {
|
|
|
365
365
|
|
|
366
366
|
// We want to fetch until our max range (5000), or until when the token minted
|
|
367
367
|
const fromBlock = toBlock - MAX_BLOCK_RANGE > mintedAtBlock ? toBlock - MAX_BLOCK_RANGE : mintedAtBlock
|
|
368
|
-
console.
|
|
368
|
+
console.info(`Backfilling token mints blocks ${fromBlock}-${toBlock}`)
|
|
369
369
|
|
|
370
370
|
// Finally, we update our database
|
|
371
371
|
this.addTokenMints(token, await this.loadMintEvents(token, fromBlock, toBlock), 'append')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visualizevalue/mint-app-base",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"postcss-preset-env": "^9.5.13",
|
|
22
22
|
"viem": "2.x",
|
|
23
23
|
"vite": "^5.4.3",
|
|
24
|
-
"vue-virtual-scroller": "
|
|
25
|
-
"@visualizevalue/mint-utils": "^0.0.
|
|
24
|
+
"vue-virtual-scroller": "2.0.0-beta.8",
|
|
25
|
+
"@visualizevalue/mint-utils": "^0.0.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"nuxt": "^3.13.2",
|
package/pages/[id]/create.vue
CHANGED
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
<div class="card">
|
|
54
54
|
<div>
|
|
55
55
|
<FormSelectFile @change="setImage" />
|
|
56
|
-
<p v-if="! isSmall" class="muted"
|
|
56
|
+
<p v-if="! isSmall" class="muted">
|
|
57
|
+
<small>Note: This should be a small file, prefferably a simple SVG like <a :href="`${base}example-contract-icon.svg`" target="_blank">this one (273 bytes)</a>. Try to make it less than 10kb.</small>
|
|
58
|
+
</p>
|
|
57
59
|
</div>
|
|
58
60
|
<FormGroup>
|
|
59
61
|
<FormInput v-model="title" placeholder="Title" required class="title" />
|
|
@@ -77,6 +79,7 @@
|
|
|
77
79
|
|
|
78
80
|
<script setup>
|
|
79
81
|
const config = useRuntimeConfig()
|
|
82
|
+
const base = useBaseURL()
|
|
80
83
|
const store = useOnchainStore()
|
|
81
84
|
const chainId = useMainChainId()
|
|
82
85
|
|
package/utils/time.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const BLOCKS_PER_DAY = 7200n // 300n * 24n (300 blocks per hour * 24 hours)
|
|
1
|
+
import { BLOCKS_PER_CACHE, BLOCKS_PER_HOUR, BLOCKS_PER_DAY } from '@visualizevalue/mint-utils/time'
|
|
2
|
+
import { blocksToSeconds, delay, nowInSeconds } from '@visualizevalue/mint-utils'
|
|
4
3
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export const nowInSeconds = (): number => Math.floor(Date.now() / 1000)
|
|
4
|
+
export {
|
|
5
|
+
BLOCKS_PER_CACHE, BLOCKS_PER_HOUR, BLOCKS_PER_DAY,
|
|
6
|
+
blocksToSeconds, delay, nowInSeconds,
|
|
7
|
+
}
|
|
10
8
|
|
|
11
9
|
const now = ref(nowInSeconds())
|
|
12
10
|
let nowInterval: NodeJS.Timeout
|