@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 +16 -2
- package/assets/styles/forms.css +17 -14
- package/components/Embed.vue +36 -2
- package/components/Form/SelectFile.vue +8 -1
- package/components/Mint/Detail.client.vue +2 -0
- package/components/Mint/Renderer/Animation.client.vue +152 -0
- package/components/Renderer/InstallButton.vue +1 -1
- package/composables/collections.ts +1 -1
- package/locales/en.json +1 -1
- package/package.json +1 -1
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: '
|
|
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: '
|
|
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
|
})
|
package/assets/styles/forms.css
CHANGED
|
@@ -124,22 +124,25 @@ select.select:--highlight {
|
|
|
124
124
|
form {
|
|
125
125
|
display: grid;
|
|
126
126
|
gap: var(--spacer);
|
|
127
|
+
}
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
+
> span:first-child {
|
|
141
|
+
display: block;
|
|
142
|
+
}
|
|
140
143
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
+
&:hover {
|
|
145
|
+
color: var(--color);
|
|
144
146
|
}
|
|
145
147
|
}
|
|
148
|
+
|
package/components/Embed.vue
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
35
|
+
accept: props.accept,
|
|
29
36
|
multiple: false,
|
|
30
37
|
})
|
|
31
38
|
|
|
@@ -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(() =>
|
|
36
|
+
const installRequest = computed(() => () => {
|
|
37
37
|
return writeContract($wagmi, {
|
|
38
38
|
abi: MINT_ABI,
|
|
39
39
|
chainId,
|
package/locales/en.json
CHANGED