@visualizevalue/mint-app-base 0.1.47 → 0.1.49

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 ADDED
@@ -0,0 +1,12 @@
1
+ export default defineAppConfig({
2
+ knownRenderers: [
3
+ {
4
+ component: 'P5',
5
+ name: 'P5 Renderer',
6
+ version: 1n,
7
+ address: '0xf6f1f8ea7bbc82a3b3da6fba1c24408e7a9a8fab',
8
+ description: 'Allows using P5 scripts as the artifact content'
9
+ },
10
+ ],
11
+ })
12
+
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <div class="code-editor">
3
+ <CodeMirror
4
+ :value="modelValue"
5
+ :options="cmOptions"
6
+ height="100%"
7
+ :placeholder="placeholder"
8
+ @change="$emit('update:modelValue', $event)"
9
+ original-style
10
+ />
11
+ </div>
12
+ </template>
13
+
14
+ <script setup>
15
+ import CodeMirror from 'codemirror-editor-vue3'
16
+ import 'codemirror/addon/display/placeholder.js'
17
+ import 'codemirror/mode/htmlmixed/htmlmixed.js'
18
+ import 'codemirror/mode/javascript/javascript.js'
19
+ // import 'codemirror/theme/ayu-mirage.css'
20
+
21
+ const props = defineProps({
22
+ modelValue: String,
23
+ placeholder: String,
24
+ mode: {
25
+ type: String,
26
+ default: 'text/javascript'
27
+ },
28
+ })
29
+ const emit = defineEmits(['update:modelValue'])
30
+
31
+ const cmOptions = computed(() => ({
32
+ mode: props.mode,
33
+ // theme: 'ayu-mirage',
34
+ indentUnit: 2,
35
+ tabSize: 2,
36
+ indentWithTab: false
37
+ }))
38
+ </script>
39
+
40
+ <style scoped>
41
+ .code-editor {
42
+ text-transform: none;
43
+ height: 100%;
44
+
45
+ :deep(.CodeMirror) {
46
+ height: 100%;
47
+ }
48
+ }
49
+ </style>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <menu v-if="ownedByMe">
3
+ <CollectionWithdraw :collection="collection" />
4
+ <Button
5
+ :to="{ name: 'id-collection-mint', params: { id, collection: collection.address } }"
6
+ id="mint-new"
7
+ >
8
+ <Icon type="add" />
9
+ <span>Mint</span>
10
+ </Button>
11
+ </menu>
12
+ </template>
13
+
14
+ <script setup>
15
+ const { collection } = defineProps({ collection: Object })
16
+
17
+ const id = useArtistId()
18
+ const ownedByMe = useIsMeCheck(collection.owner)
19
+ </script>
@@ -26,16 +26,7 @@
26
26
  </p>
27
27
  </div>
28
28
 
29
- <menu v-if="ownedByMe">
30
- <CollectionWithdraw :collection="collection" />
31
- <Button
32
- :to="{ name: 'id-collection-mint', params: { id, collection: collection.address } }"
33
- id="mint-new"
34
- >
35
- <Icon type="add" />
36
- <span>Mint New</span>
37
- </Button>
38
- </menu>
29
+ <CollectionActions :collection="collection" />
39
30
  </div>
40
31
  </slot>
41
32
  </header>
@@ -48,8 +39,6 @@ const { collection } = defineProps<{
48
39
 
49
40
  const id = useArtistId()
50
41
  const store = useOnchainStore()
51
-
52
- const ownedByMe = useIsMeCheck(collection.owner)
53
42
  </script>
54
43
 
55
44
  <style scoped>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div class="embed">
3
+ <iframe
4
+ ref="frame"
5
+ frameborder="0"
6
+ :src="src"
7
+ sandbox="allow-scripts"
8
+ ></iframe>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup>
13
+ defineProps({
14
+ src: String,
15
+ })
16
+ </script>
17
+
18
+ <style scoped>
19
+ .embed {
20
+ width: 100%;
21
+ height: 0;
22
+ padding-bottom: 100%;
23
+ position: relative;
24
+
25
+ iframe {
26
+ width: 100%;
27
+ height: 100%;
28
+ position: absolute;
29
+ border: var(--border);
30
+ }
31
+ }
32
+ </style>
33
+
@@ -13,7 +13,7 @@
13
13
  width: 100%;
14
14
  }
15
15
 
16
- fieldset {
16
+ .fieldset-wrapper > fieldset {
17
17
  width: 100%;
18
18
  max-width: -webkit-fill-available;
19
19
  display: flex;
@@ -31,6 +31,10 @@ const { files, open, reset, onChange } = useFileDialog({
31
31
 
32
32
  const file = computed(() => files.value?.length ? files.value[0] : null)
33
33
  onChange(() => emit('change', file.value))
34
+
35
+ defineExpose({
36
+ reset,
37
+ })
34
38
  </script>
35
39
 
36
40
  <style scoped>
@@ -16,6 +16,7 @@ const ICONS = {
16
16
  'chevron-right': '➡️',
17
17
  'chevron-up': '⬆️',
18
18
  'close': '✖️',
19
+ 'code': '🩻',
19
20
  'discord': '🤖',
20
21
  'edit': '📝',
21
22
  'email': '📧',
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <Actions class="borderless">
3
+ <Button @click="mint">Mint</Button>
4
+ </Actions>
5
+
6
+ <TransactionFlow
7
+ ref="txFlow"
8
+ :text="{
9
+ title: {
10
+ chain: 'Switch Chain',
11
+ requesting: 'Confirm In Wallet',
12
+ waiting: 'Transaction Submitted',
13
+ complete: 'Success!'
14
+ },
15
+ lead: {
16
+ chain: 'Requesting to switch chain...',
17
+ requesting: 'Requesting Signature...',
18
+ waiting: 'Checking mint Transaction...',
19
+ complete: `New token minted...`,
20
+ },
21
+ action: {
22
+ confirm: 'Mint',
23
+ error: 'Retry',
24
+ complete: 'OK',
25
+ },
26
+ }"
27
+ skip-confirmation
28
+ auto-close-success
29
+ @complete="mintCreated"
30
+ />
31
+ </template>
32
+
33
+ <script setup>
34
+ const props = defineProps({
35
+ collection: Object,
36
+ })
37
+
38
+ const txFlow = ref()
39
+
40
+ const { mint, mintCreated } = useCreateMintFlow(props.collection, txFlow)
41
+ </script>
42
+
43
+ <style scoped>
44
+ menu {
45
+ justify-content: flex-end;
46
+
47
+ @media (--md) {
48
+ grid-column: 2;
49
+ }
50
+ }
51
+ </style>
@@ -0,0 +1,49 @@
1
+ <script setup>
2
+ import Base from './Renderer/Base.client.vue'
3
+ import P5 from './Renderer/P5.client.vue'
4
+
5
+ const components = {
6
+ Base,
7
+ P5,
8
+ }
9
+
10
+ const props = defineProps(['collection'])
11
+ const { component } = useCreateMintRendererComponent(props.collection)
12
+ </script>
13
+
14
+ <template>
15
+ <div class="mint-detail">
16
+ <MintSelectRenderer :collection="collection" class="borderless" />
17
+
18
+ <MintPreview />
19
+
20
+ <component :is="components[component]" class="card" />
21
+
22
+ <MintAction :collection="collection" />
23
+ </div>
24
+ </template>
25
+
26
+ <style>
27
+ .mint-detail {
28
+ display: grid;
29
+ gap: var(--spacer);
30
+
31
+ > *:not(.borderless) {
32
+ border: var(--border);
33
+ padding: var(--spacer);
34
+ }
35
+
36
+ @media (--md) {
37
+ grid-template-columns: 40% 1fr;
38
+
39
+ .mint-select-renderer {
40
+ grid-column: span 2;
41
+ }
42
+ }
43
+
44
+ @media (--lg) {
45
+ grid-template-columns: repeat(2, minmax(0, 1fr));
46
+ }
47
+ }
48
+ </style>
49
+
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <article class="mint-preview">
3
+ <div class="static">
4
+ <Image v-if="image" :src="image" alt="Preview" />
5
+ <ImagePreview v-else />
6
+ </div>
7
+
8
+ <Embed v-if="animationUrl" :src="animationUrl" />
9
+
10
+ <h1 :class="{ '': !name }">{{ name || 'Token' }}</h1>
11
+ <p :class="{ '': !description }">
12
+ {{ description || 'No description' }}
13
+ </p>
14
+ </article>
15
+ </template>
16
+
17
+ <script setup>
18
+ const { image, animationUrl, name, description } = useCreateMintData()
19
+ </script>
20
+
21
+ <style scoped>
22
+ .mint-preview {
23
+ height: 100%;
24
+ place-content: center;
25
+
26
+ svg {
27
+ box-shadow: var(--border-shadow);
28
+ }
29
+
30
+ .image,
31
+ svg {
32
+ margin-bottom: var(--spacer-sm);
33
+ width: 100%;
34
+ }
35
+
36
+ .static {
37
+ &:has(+ .embed) {
38
+ width: 30%;
39
+ }
40
+ }
41
+
42
+ .embed {
43
+ margin: var(--spacer-sm) 0;
44
+ }
45
+
46
+ h1 {
47
+ display: flex;
48
+ gap: var(--spacer-sm);
49
+ align-items: baseline;
50
+ font-size: var(--font-lg);
51
+ }
52
+
53
+ p {
54
+ color: var(--muted);
55
+ }
56
+ }
57
+ </style>
58
+
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <div class="mint-renderer-base">
3
+ <Actions>
4
+ <select class="select choose-mode" v-model="mode">
5
+ <option value="file" title="Data URI Encoded File Upload">DATA-URI</option>
6
+ <option value="ipfs" title="Interplanetary File System">IPFS</option>
7
+ <option value="ar" title="Arweave">ARWEAVE</option>
8
+ <option value="http" title="Hypertext Transfer Protocol" disabled>HTTP</option>
9
+ <option value="svg" title="Scalable Vector Graphic" disabled>SVG</option>
10
+ </select>
11
+ </Actions>
12
+
13
+ <div>
14
+ <div v-if="mode === 'file'">
15
+ <FormSelectFile ref="select" @change="setArtifact" />
16
+ <p v-if="! isSmall" class="muted">
17
+ <small>
18
+ Note: This should be a small file, prefferably an SVG like <a href="https://presence.art/tokens/perspective.svg" target="_blank">this one (810 bytes)</a>.
19
+ If it is larger than what we can store within one transaction, the token creation will be split up into multiple transactions.
20
+ </small>
21
+ </p>
22
+ </div>
23
+ <FormInput v-else-if="mode === 'ipfs'" v-model="ipfsCid" placeholder="CID (qmx...)" prefix="ipfs://" required />
24
+ <FormInput v-else-if="mode === 'ar'" v-model="arTxId" placeholder="TX ID (frV...)" prefix="ar://" required />
25
+
26
+ <FormInput v-model="name" placeholder="Title" required />
27
+ <FormInput v-model="description" placeholder="Description" />
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup>
33
+ const props = defineProps({
34
+ decoupleArtifact: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
38
+ })
39
+
40
+ const {
41
+ artifact,
42
+ image,
43
+ name,
44
+ description,
45
+ } = useCreateMintData()
46
+
47
+ const select = ref()
48
+ const mode = ref('file')
49
+ const ipfsCid = ref('')
50
+ const arTxId= ref('')
51
+
52
+ const imageSize = ref(0)
53
+ const isSmall = computed(() => imageSize.value / 1024 < 10)
54
+ const setArtifact = async (file) => {
55
+ try {
56
+ image.value = await imageFileToDataUri(file)
57
+ imageSize.value = file.size
58
+ } catch (e) {
59
+ image.value = ''
60
+ imageSize.value = 0
61
+ }
62
+ }
63
+ watch(ipfsCid, () => {
64
+ const validated = validateCID(ipfsCid.value)
65
+ if (! validated) {
66
+ image.value = ''
67
+ } else {
68
+ image.value = ipfsToHttpURI(`ipfs://${validated}`)
69
+ }
70
+ })
71
+ watch(arTxId, () => {
72
+ image.value = `https://arweave.net/${arTxId.value}`
73
+ })
74
+ watch(mode, () => image.value = '')
75
+
76
+ watch(image, () => {
77
+ if (props.decoupleArtifact) return
78
+
79
+ // Copy to image (simple and stupid for the base renderer...)
80
+ artifact.value = image.value
81
+
82
+ // If artifact is empty, reset the select field
83
+ if (! artifact.value) select.value.reset()
84
+ })
85
+ </script>
86
+
87
+ <style scoped>
88
+ .mint-renderer-base {
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: var(--spacer);
92
+ height: 100%;
93
+
94
+ > div {
95
+ display: flex;
96
+ height: 100%;
97
+ flex-direction: column;
98
+ justify-content: center;
99
+ gap: var(--spacer);
100
+ }
101
+ }
102
+
103
+ select.choose-mode {
104
+ width: fit-content;
105
+ }
106
+ </style>
107
+
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <div class="mint-renderer-p5">
3
+
4
+ <Tabs initial="base">
5
+ <template #menu="{ active, select }">
6
+ <Button @click="select('base')" :class="{ active: active === 'base' }">Static</Button>
7
+ <Button @click="select('script')" :class="{ active: active === 'script' }">P5 Script</Button>
8
+ </template>
9
+ <template #content="{ active }">
10
+ <MintRendererBase v-show="active === 'base'" decouple-artifact />
11
+ <CodeEditor v-show="active === 'script'" v-model="script" class="full" />
12
+ </template>
13
+ </Tabs>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup>
18
+ const {
19
+ artifact,
20
+ image,
21
+ animationUrl,
22
+ } = useCreateMintData()
23
+
24
+ const script = ref(DEFAULT_P5_SCRIPT)
25
+
26
+ // Keep the animationURL (for the preview) up to date
27
+ watchEffect(() => {
28
+ animationUrl.value = getP5HtmlUri('Preview', script.value)
29
+ })
30
+
31
+ // Encode the artifact as per how the P5Renderer.sol contract expects it.
32
+ watchEffect(() => {
33
+ artifact.value = encodeAbiParameters(
34
+ [ { type: 'string', name: 'image' }, { type: 'string', name: 'script' } ],
35
+ [ image.value, script.value ],
36
+ )
37
+ })
38
+ </script>
39
+
40
+ <style>
41
+ .mint-renderer-p5 {
42
+ padding: 0 !important;
43
+ border: 0 !important;
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 0;
47
+
48
+ > .tabs {
49
+ height: min-content;
50
+ }
51
+
52
+ > .tabs-content {
53
+ border: var(--border);
54
+ border-top: 0;
55
+ height: 100%;
56
+
57
+ > * {
58
+ height: 100%;
59
+ }
60
+
61
+ > *:not(.full) {
62
+ padding: var(--spacer);
63
+ }
64
+ }
65
+ }
66
+ </style>
67
+
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <aside class="mint-select-renderer">
3
+ <select
4
+ class="select"
5
+ v-model="selection"
6
+ >
7
+ <option
8
+ v-for="( renderer, index ) in collection.renderers"
9
+ :value="index"
10
+ :title="renderer.name"
11
+ >{{ renderer.name }}</option>
12
+ <option disabled>----</option>
13
+ <option value="new">Install New</option>
14
+ </select>
15
+ </aside>
16
+ </template>
17
+
18
+ <script setup>
19
+ const { collection } = defineProps(['collection'])
20
+ const id = useArtistId()
21
+
22
+ const store = useOnchainStore()
23
+ onMounted(() => store.fetchCollectionRenderers(collection.address))
24
+
25
+ const { renderer, reset } = useCreateMintData()
26
+ const selection = ref(renderer.value)
27
+ watch(selection, () => {
28
+ if (selection.value === 'new') {
29
+ return navigateTo({ name: 'id-collection-renderers', params: { id: id.value, collection: collection.address } })
30
+ }
31
+
32
+ renderer.value = selection.value
33
+
34
+ reset()
35
+ })
36
+ </script>
37
+
38
+ <style scoped>
39
+ .mint-select-renderer {
40
+ display: grid;
41
+ gap: var(--spacer);
42
+
43
+ @media (--md) {
44
+ display: flex;
45
+ justify-content: space-between;
46
+
47
+ .select {
48
+ width: fit-content;
49
+ }
50
+ }
51
+ }
52
+ </style>
@@ -33,6 +33,7 @@ watch(props, () => updateBreadcrumbs())
33
33
  &.inset {
34
34
  gap: var(--spacer);
35
35
  padding: var(--spacer);
36
+ overflow-x: hidden;
36
37
 
37
38
  > *:not(.borderless) {
38
39
  border: var(--border);
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <Button @click="install">Install</Button>
3
+ <TransactionFlow
4
+ ref="txFlow"
5
+ :request="installRequest"
6
+ :text="{
7
+ title: {
8
+ chain: 'Switch Chain',
9
+ requesting: 'Confirm In Wallet',
10
+ waiting: 'Transaction Submitted',
11
+ complete: 'Success!'
12
+ },
13
+ lead: {
14
+ chain: 'Requesting to switch chain...',
15
+ requesting: 'Requesting Signature...',
16
+ waiting: 'Checking Transaction...',
17
+ complete: `New Renderer registered...`,
18
+ },
19
+ action: {
20
+ confirm: 'Register Renderer',
21
+ error: 'Retry',
22
+ complete: 'OK',
23
+ },
24
+ }"
25
+ skip-confirmation
26
+ auto-close-success
27
+ />
28
+ </template>
29
+
30
+ <script setup>
31
+ const { $wagmi } = useNuxtApp()
32
+ const { collection, renderer } = defineProps(['collection', 'renderer'])
33
+ const store = useOnchainStore()
34
+ const chainId = useMainChainId()
35
+
36
+ const installRequest = computed(() => async () => {
37
+ return writeContract($wagmi, {
38
+ abi: MINT_ABI,
39
+ chainId,
40
+ address: collection.address,
41
+ functionName: 'registerRenderer',
42
+ args: [
43
+ renderer.address,
44
+ ],
45
+ })
46
+ })
47
+ const txFlow = ref()
48
+ const installing = ref(false)
49
+ const install = async () => {
50
+ installing.value = true
51
+
52
+ try {
53
+ await txFlow.value.initializeRequest(installRequest.value)
54
+
55
+ await store.fetchCollectionRenderers(collection.address)
56
+ } catch (e) {
57
+ console.error(e)
58
+ }
59
+
60
+ installing.value = false
61
+ }
62
+ </script>