@visualizevalue/mint-app-base 0.1.80 → 0.1.82

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/app.config.ts CHANGED
@@ -6,9 +6,16 @@ export default defineAppConfig({
6
6
  component: 'P5',
7
7
  name: 'P5 Renderer',
8
8
  version: 1n,
9
- address: '0x32B8Ffa14e7F77c252b6D43BEC5498FCef2b205F',
9
+ address: '0x32b8ffa14e7f77c252b6d43bec5498fcef2b205f',
10
10
  description: 'Allows using P5 scripts as the artifact content'
11
11
  },
12
+ // {
13
+ // component: 'Animation',
14
+ // name: 'Animation Renderer',
15
+ // version: 1n,
16
+ // address: '', // TODO: Deploy...
17
+ // description: 'Allows linking to both an image and an animation url'
18
+ // },
12
19
  ],
13
20
  // Sepolia
14
21
  11155111: [
@@ -16,9 +23,16 @@ export default defineAppConfig({
16
23
  component: 'P5',
17
24
  name: 'P5 Renderer (Sepolia)',
18
25
  version: 1n,
19
- address: '0xfaDF2fB2F8a15Fc830c176B71D2c905E95f4169e',
26
+ address: '0xfadf2fb2f8a15fc830c176b71d2c905e95f4169e',
20
27
  description: 'Allows using P5 scripts as the artifact content'
21
28
  },
29
+ {
30
+ component: 'Animation',
31
+ name: 'Animation Renderer',
32
+ version: 1n,
33
+ address: '0xeeaf428251c477002d52c69e022b357a91f36517',
34
+ description: 'Allows linking to both an image and an animation url'
35
+ },
22
36
  ],
23
37
  }
24
38
  })
@@ -124,22 +124,25 @@ select.select:--highlight {
124
124
  form {
125
125
  display: grid;
126
126
  gap: var(--spacer);
127
+ }
127
128
 
128
- label {
129
- display: block;
130
- font-family: var(--ui-font-family);
131
- text-transform: var(--text-transform-ui);
132
- margin: var(--size-2) 0;
133
- color: var(--muted);
134
- transition: all var(--speed);
129
+ form label,
130
+ label.form-label,
131
+ label:has(.form-item) {
132
+ font-family: var(--ui-font-family);
133
+ font-size: var(--ui-font-size);
134
+ text-transform: var(--text-transform-ui);
135
+ margin: var(--size-2) 0;
136
+ transition: all var(--speed);
137
+ display: grid;
138
+ gap: var(--size-2);
135
139
 
136
- > span:first-child {
137
- display: block;
138
- margin: 0 0 var(--size-2) 0
139
- }
140
+ > span:first-child {
141
+ display: block;
142
+ }
140
143
 
141
- &:hover {
142
- color: var(--color);
143
- }
144
+ &:hover {
145
+ color: var(--color);
144
146
  }
145
147
  }
148
+
@@ -3,7 +3,12 @@
3
3
  class="embed"
4
4
  @touchmove.stop.prevent="() => null"
5
5
  >
6
+ <video v-if="isPlayable" autoplay muted playsinline loop crossorigin="anonymous" >
7
+ <source :src="src" :type="mediaType">
8
+ Your browser does not support the video tag.
9
+ </video>
6
10
  <iframe
11
+ v-else
7
12
  ref="frame"
8
13
  frameborder="0"
9
14
  :src="src"
@@ -12,17 +17,46 @@
12
17
  </div>
13
18
  </template>
14
19
 
15
- <script setup>
20
+ <script setup lang="ts">
16
21
  import { useWindowSize } from '@vueuse/core'
17
22
 
23
+ async function fetchMediaType(url: string): Promise<string | null> {
24
+ try {
25
+ // Send a HEAD request to get only the headers
26
+ const response = await fetch(url, { method: 'HEAD' })
27
+
28
+ // Check if the request was successful
29
+ if (!response.ok) {
30
+ console.error(`Failed to fetch media type. Status: ${response.status}`)
31
+ return null
32
+ }
33
+
34
+ // Get the Content-Type header
35
+ const contentType = response.headers.get('Content-Type')
36
+
37
+ // Return the media type or null if unavailable
38
+ return contentType ? contentType.split(';')[0] : null
39
+ } catch (error) {
40
+ console.error(`Error fetching media type: ${error}`)
41
+ return null
42
+ }
43
+ }
44
+
18
45
  const props = defineProps({
19
46
  src: String,
20
47
  })
21
48
 
22
49
  // Update on input change
23
50
  const src = ref(props.src)
24
- watch(props, () => {
51
+ const mediaType = ref()
52
+ const isPlayable = computed(() => {
53
+ if (! mediaType.value) return false
54
+ return document.createElement('video').canPlayType(mediaType.value) !== ""
55
+ })
56
+
57
+ watchEffect(async () => {
25
58
  src.value = props.src
59
+ mediaType.value = await fetchMediaType(src.value)
26
60
  })
27
61
 
28
62
  // Force reload on resize
@@ -20,12 +20,19 @@
20
20
  <script setup lang="ts">
21
21
  import { useFileDialog } from '@vueuse/core'
22
22
 
23
+ const props = defineProps({
24
+ accept: {
25
+ type: String,
26
+ default: 'image/*'
27
+ }
28
+ })
29
+
23
30
  const emit = defineEmits<{
24
31
  change: [file: File|null|undefined]
25
32
  }>()
26
33
 
27
34
  const { files, open, reset, onChange } = useFileDialog({
28
- accept: 'image/*',
35
+ accept: props.accept,
29
36
  multiple: false,
30
37
  })
31
38
 
@@ -1,8 +1,10 @@
1
1
  <script setup>
2
+ import Animation from './Renderer/Animation.client.vue'
2
3
  import Base from './Renderer/Base.client.vue'
3
4
  import P5 from './Renderer/P5.client.vue'
4
5
 
5
6
  const components = {
7
+ Animation,
6
8
  Base,
7
9
  P5,
8
10
  }
@@ -0,0 +1,152 @@
1
+ <template>
2
+ <div class="mint-renderer-base">
3
+ <Actions>
4
+ <select class="select choose-mode" v-model="mode">
5
+ <option value="ipfs" title="Interplanetary File System">IPFS Identifier</option>
6
+ <option value="ar" title="Arweave">Arweave Transaction</option>
7
+ <option value="file" title="Data URI Encoded File Upload">Onchain Artifact</option>
8
+ </select>
9
+ </Actions>
10
+
11
+ <div>
12
+ <label class="form-label">
13
+ <span>Image</span>
14
+
15
+ <FormInput v-if="mode === 'ipfs'" v-model="imageIpfsCid" placeholder="CID (qmx...)" prefix="ipfs://" required />
16
+ <FormInput v-else-if="mode === 'ar'" v-model="imageArTxId" placeholder="TX ID (frV...)" prefix="ar://" required />
17
+ <div v-else-if="mode === 'file'">
18
+ <FormSelectFile ref="imageSelect" @change="setImageArtifact" />
19
+ <p v-if="! imageIsSmall" class="muted">
20
+ <small>
21
+ {{ $t('mint.base.note.start') }}
22
+ <a href="https://presence.art/tokens/perspective.svg" target="_blank">{{ $t('mint.base.note.link_text') }}</a>.
23
+ {{ $t('mint.base.note.end') }}
24
+ </small>
25
+ </p>
26
+ </div>
27
+ </label>
28
+
29
+ <label class="form-label">
30
+ <span>Animation</span>
31
+
32
+ <FormInput v-if="mode === 'ipfs'" v-model="animationIpfsCid" placeholder="CID (qmx...)" prefix="ipfs://" required />
33
+ <FormInput v-else-if="mode === 'ar'" v-model="animationArTxId" placeholder="TX ID (frV...)" prefix="ar://" required />
34
+ <div v-else-if="mode === 'file'">
35
+ <FormSelectFile ref="animationSelect" @change="setAnimationArtifact" accept="video/*" />
36
+ <p v-if="! animationIsSmall" class="muted">
37
+ <small>
38
+ {{ $t('mint.base.note.start') }}
39
+ <a href="https://presence.art/tokens/perspective.svg" target="_blank">{{ $t('mint.base.note.link_text') }}</a>.
40
+ {{ $t('mint.base.note.end') }}
41
+ </small>
42
+ </p>
43
+ </div>
44
+ </label>
45
+
46
+ <FormInput v-model="name" :placeholder="$t('mint.base.title_placeholder')" required />
47
+ <FormInput v-model="description" :placeholder="$t('mint.base.description_placeholder')" />
48
+ </div>
49
+ </div>
50
+ </template>
51
+
52
+ <script setup>
53
+ const {
54
+ artifact,
55
+ image,
56
+ name,
57
+ animationUrl,
58
+ description,
59
+ } = useCreateMintData()
60
+
61
+ const mode = ref('ipfs')
62
+
63
+ const imageSelect = ref()
64
+ const imageIpfsCid = ref('')
65
+ const imageArTxId= ref('')
66
+
67
+ const animationSelect = ref()
68
+ const animationIpfsCid = ref('')
69
+ const animationArTxId= ref('')
70
+
71
+ const imageSize = ref(0)
72
+ const imageIsSmall = computed(() => imageSize.value / 1024 < 10)
73
+ const setImageArtifact = async (file) => {
74
+ try {
75
+ image.value = await imageFileToDataUri(file)
76
+ imageSize.value = file.size
77
+ } catch (e) {
78
+ image.value = ''
79
+ imageSize.value = 0
80
+ }
81
+ }
82
+ const animationSize = ref(0)
83
+ const animationIsSmall = computed(() => animationSize.value / 1024 < 10)
84
+ const setAnimationArtifact = async (file) => {
85
+ try {
86
+ animationUrl.value = await imageFileToDataUri(file)
87
+ animationSize.value = file.size
88
+ } catch (e) {
89
+ animationUrl.value = ''
90
+ animationSize.value = 0
91
+ }
92
+ }
93
+
94
+ watch(imageIpfsCid, () => {
95
+ const validated = validateCID(imageIpfsCid.value)
96
+ if (! validated) {
97
+ image.value = ''
98
+ } else {
99
+ image.value = ipfsToHttpURI(`ipfs://${validated}`)
100
+ }
101
+ })
102
+ watch(imageArTxId, () => {
103
+ image.value = `https://arweave.net/${imageArTxId.value}`
104
+ })
105
+ watch(mode, () => image.value = '')
106
+
107
+ watch(animationIpfsCid, () => {
108
+ const validated = validateCID(animationIpfsCid.value)
109
+ if (! validated) {
110
+ animationUrl.value = ''
111
+ } else {
112
+ animationUrl.value = ipfsToHttpURI(`ipfs://${validated}`)
113
+ }
114
+ })
115
+ watch(animationArTxId, () => {
116
+ animationUrl.value = `https://arweave.net/${animationArTxId.value}`
117
+ })
118
+ watch(mode, () => {
119
+ image.value = ''
120
+ animationUrl.value = ''
121
+ })
122
+
123
+ // Encode the artifact as per how the P5Renderer.sol contract expects it.
124
+ watchEffect(() => {
125
+ artifact.value = encodeAbiParameters(
126
+ [ { type: 'string', name: 'image' }, { type: 'string', name: 'animation' } ],
127
+ [ image.value, animationUrl.value ],
128
+ )
129
+ })
130
+ </script>
131
+
132
+ <style scoped>
133
+ .mint-renderer-base {
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: var(--spacer);
137
+ height: 100%;
138
+
139
+ > div {
140
+ display: flex;
141
+ height: 100%;
142
+ flex-direction: column;
143
+ justify-content: center;
144
+ gap: var(--spacer);
145
+ }
146
+ }
147
+
148
+ select.choose-mode {
149
+ width: fit-content;
150
+ }
151
+ </style>
152
+
@@ -33,7 +33,7 @@ const { collection, renderer } = defineProps(['collection', 'renderer'])
33
33
  const store = useOnchainStore()
34
34
  const chainId = useMainChainId()
35
35
 
36
- const installRequest = computed(() => async () => {
36
+ const installRequest = computed(() => () => {
37
37
  return writeContract($wagmi, {
38
38
  abi: MINT_ABI,
39
39
  chainId,
@@ -276,7 +276,7 @@ export const useOnchainStore = () => {
276
276
  address,
277
277
  functionName: 'latestTokenId',
278
278
  chainId,
279
- })
279
+ }) as Promise<bigint>
280
280
 
281
281
  const collection = this.collection(address)
282
282
 
package/locales/en.json CHANGED
@@ -93,7 +93,7 @@
93
93
  },
94
94
  "note": {
95
95
  "start": "Note: This should be a small file, preferably a simple SVG like",
96
- "link_text": "this one (273 bytes)",
96
+ "link_text": "this one (153 bytes)",
97
97
  "end": "Try to make it less than 10kb."
98
98
  },
99
99
  "preview": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visualizevalue/mint-app-base",
3
- "version": "0.1.80",
3
+ "version": "0.1.82",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "dependencies": {