@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.
@@ -18,7 +18,11 @@
18
18
  @click="() => login(connector)"
19
19
  class="choose-connector-button"
20
20
  >
21
- <img v-if="ICONS[connector.name]" :src="connector.icon || `/icons/wallets/${ICONS[connector.name]}`" alt="">
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
- <div v-if="currentBlock" class="token-mint-timeline-items">
7
- <TokenMintTimelineItem
8
- v-for="mint of mints"
9
- :mint="mint"
10
- :key="mint.tx"
11
- :block="currentBlock"
12
- />
13
- <TokenMintTimelineItem v-if="backfillComplete">
14
- <Account :address="collection.owner" class="account" />
15
-
16
- <span class="amount">1<span>×</span></span>
17
- <span class="price">Artist Mint</span>
18
-
19
- <span class="time-ago"><BlocksTimeAgo v-if="currentBlock" :blocks="currentBlock - mintedAtBlock" /></span>
20
-
21
- <span class="links">
22
- <NuxtLink :to="`${config.public.blockExplorer}/nft/${token.collection}/${token.tokenId}`" target="_blank">
23
- <Icon type="link" />
24
- </NuxtLink>
25
- </span>
26
- </TokenMintTimelineItem>
27
- </div>
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 state.backfillTokenMints(token)
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(currentBlock, () => {
70
- if (loading.value) return
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
- state.fetchTokenMints(token)
95
+ backfill()
73
96
  })
74
97
 
75
- const backfill = async () => {
76
- loading.value = true
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
- backfill()
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" class="account" />
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
+
@@ -0,0 +1,7 @@
1
+ export const useBaseURL = () => {
2
+ const config = useRuntimeConfig()
3
+
4
+ return config.app.baseURL.endsWith('/')
5
+ ? config.app.baseURL
6
+ : config.app.baseURL + '/'
7
+ }
@@ -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 = 2500n
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.log(`Backfilling token mints blocks ${fromBlock}-${toBlock}`)
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.44",
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": "^2.0.0-beta.8",
25
- "@visualizevalue/mint-utils": "^0.0.2"
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",
@@ -53,7 +53,9 @@
53
53
  <div class="card">
54
54
  <div>
55
55
  <FormSelectFile @change="setImage" />
56
- <p v-if="! isSmall" class="muted"><small>Note: This should be a small file, prefferably a simple SVG like <a href="/example-contract-icon.svg" target="_blank">this one (273 bytes)</a>. Try to make it less than 10kb.</small></p>
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
- export const BLOCKS_PER_CACHE = 5n * 30n // 30 minutes (5 blocks each)
2
- export const BLOCKS_PER_HOUR = 300n // 5n * 60n * 24n (5 blocks/min * 60 min * 24 hours)
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 const delay = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms))
6
-
7
- export const blocksToSeconds = (blocks: bigint): number => Number(blocks * 12n)
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