Skip to content
This repository was archived by the owner on Dec 5, 2025. It is now read-only.

Commit 60b89af

Browse files
authored
Merge pull request #200 from kodadot/model-view
model view
2 parents 9d36ee6 + 7bc366c commit 60b89af

File tree

11 files changed

+144
-5
lines changed

11 files changed

+144
-5
lines changed

dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@fortawesome/free-regular-svg-icons": "^5.15.2",
1717
"@fortawesome/free-solid-svg-icons": "^5.15.2",
1818
"@fortawesome/vue-fontawesome": "^2.0.2",
19+
"@google/model-viewer": "^1.6.0",
1920
"@polkadot/extension-dapp": "^0.36.1",
2021
"@polkadot/types": "^3.7.2",
2122
"@polkadot/ui-keyring": "^0.68.1",

dashboard/src/components/rmrk/Gallery/GalleryItem.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ export default class GalleryItem extends Vue {
157157
this.nft = {
158158
...nft,
159159
image: sanitizeIpfsUrl(nft.image || ''),
160-
animation_url: sanitizeIpfsUrl(nft.animation_url || '', 'pinata')
160+
animation_url: sanitizeIpfsUrl(nft.animation_url || '', 'dweb')
161161
};
162162
if (this.nft.animation_url) {
163163
const { headers } = await api.head(this.nft.animation_url);
164164
this.mimeType = headers['content-type'];
165165
const mediaType = resolveMedia(this.mimeType);
166-
this.imageVisible = ![MediaType.VIDEO, MediaType.IMAGE].some(
166+
this.imageVisible = ![MediaType.VIDEO, MediaType.IMAGE, MediaType.MODEL].some(
167167
t => t === mediaType
168168
);
169169
}

dashboard/src/components/rmrk/Gallery/Item/Sharing.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ import { IFrame, emptyIframe } from '../../types';
7474
@Component({})
7575
export default class Sharing extends Vue {
7676
@Prop({ default: 'Check this cool NFT on %23KusamaNetwork %23kodadot' }) label!: string;
77-
@Prop({ default: emptyIframe }) iframe!: IFrame;
77+
@Prop({ default: () => emptyIframe }) iframe!: IFrame;
7878
7979
get helloText() {
8080
return this.label;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<template>
2+
<div class="view-model__wrapper">
3+
<model-viewer
4+
class="view-model__component"
5+
:src="src"
6+
:ios-src="iosSrc"
7+
camera-controls
8+
shadow-intensity="1"
9+
ar
10+
ar-modes="scene-viewer webxr quick-look"
11+
>
12+
<div class="progress-bar hide" slot="progress-bar">
13+
<div class="update-bar"></div>
14+
</div>
15+
<button class="button is-dark" slot="ar-button" id="ar-button">
16+
AR
17+
</button>
18+
</model-viewer>
19+
</div>
20+
</template>
21+
22+
<script lang="ts" >
23+
import { Component, Prop, Vue } from 'vue-property-decorator';
24+
import '@google/model-viewer';
25+
26+
27+
28+
const testSrc = 'http://localhost:8000/Astronaut.glb';
29+
const testIosSrc = 'http://localhost:8000/Astronaut.usdz';
30+
31+
@Component({})
32+
export default class ViewModel extends Vue {
33+
@Prop({ default: testSrc }) public src!: string;
34+
@Prop({ default: testIosSrc }) public iosSrc!: string;
35+
36+
// get src() {
37+
// return 'https://kristina-simakova.github.io/ar-webview/assets/RocketShip_1393.gltf'; // }
38+
39+
get poster() {
40+
return '';
41+
}
42+
}
43+
</script>
44+
45+
<style scoped>
46+
.view-model__wrapper {
47+
height: 100%;
48+
width: 100%;
49+
}
50+
51+
.view-model__component {
52+
height: 100%;
53+
width: 100%;
54+
min-width: 300px;
55+
min-height: 300px;
56+
}
57+
</style>

dashboard/src/components/rmrk/Media/MediaResolver.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
<template>
3-
<component :is="resolveComponent" :src="src" />
3+
<component v-if="src" :is="resolveComponent" :src="src" />
44
</template>
55

66
<script lang="ts" >
@@ -12,6 +12,7 @@ const VideoMedia = () => import('./VideoMedia.vue')
1212
const ImageMedia = () => import('./ImageMedia.vue')
1313
const AudioMedia = () => import('./AudioMedia.vue')
1414
const JsonMedia = () => import('./JsonMedia.vue')
15+
const ModelMedia = () => import('./ModelMedia.vue')
1516
const Media = () => import('./Unknown.vue')
1617
const SUFFIX = 'Media'
1718
@Component({
@@ -20,6 +21,7 @@ const SUFFIX = 'Media'
2021
ImageMedia,
2122
AudioMedia,
2223
JsonMedia,
24+
ModelMedia,
2325
Media
2426
}
2527
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<template>
2+
<div class="view-model__wrapper">
3+
<model-viewer
4+
class="view-model__component"
5+
:src="src"
6+
camera-controls
7+
shadow-intensity="1"
8+
>
9+
</model-viewer>
10+
</div>
11+
</template>
12+
13+
<script lang="ts" >
14+
import { Component, Prop, Vue } from 'vue-property-decorator';
15+
import '@google/model-viewer';
16+
17+
@Component({})
18+
export default class ViewModel extends Vue {
19+
@Prop() public src!: string;
20+
21+
// get src() {
22+
// return 'https://kristina-simakova.github.io/ar-webview/assets/RocketShip_1393.gltf'; // }
23+
24+
get poster() {
25+
return '';
26+
}
27+
}
28+
</script>
29+
30+
<style scoped>
31+
.view-model__wrapper {
32+
height: 100%;
33+
width: 100%;
34+
}
35+
36+
.view-model__component {
37+
height: 100%;
38+
width: 100%;
39+
min-width: 300px;
40+
min-height: 592px;
41+
}
42+
</style>

dashboard/src/components/rmrk/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export enum RmrkEvent {
5858

5959
export enum MediaType {
6060
VIDEO = 'Video',
61+
MODEL = 'Model',
6162
IMAGE = 'Image',
6263
AUDIO = 'Audio',
6364
JSON = 'Json',

dashboard/src/components/rmrk/utils.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const ipfsProviders: Record<string, string> = {
1919
pinata: 'https://gateway.pinata.cloud/',
2020
cloudflare: 'https://cloudflare-ipfs.com/',
2121
ipfs: DEFAULT_IPFS_PROVIDER,
22+
dweb: 'https://dweb.link/',
2223
'': 'https://cloudflare-ipfs.com/'
2324
}
2425

@@ -193,7 +194,11 @@ export const resolveMedia = (mimeType: string): MediaType => {
193194
}
194195

195196
if (/^application\/json/.test(mimeType)) {
196-
return MediaType.JSON;
197+
return MediaType.MODEL;
198+
}
199+
200+
if (/^application\/octet-stream/.test(mimeType)) {
201+
return MediaType.MODEL;
197202
}
198203

199204
const match = mimeType.match(/^[a-z]+/);
@@ -221,3 +226,21 @@ export const sortByModification = (a: any, b: any) => b._mod - a._mod
221226
export const nftSort = (a: any, b: any) => b.blockNumber - a.blockNumber
222227
export const sortBy = (arr: any[], cb = nftSort) => arr.slice().sort(cb)
223228
export const defaultSortBy = (arr: any[]) => sortBy(arr)
229+
230+
231+
export const isJsonGltf = (value: any): boolean => {
232+
try {
233+
if (!(value['asset'] && /^2\.[0-9]$/.test(value['asset']['version']))) {
234+
return false
235+
}
236+
237+
if (!(value['buffers'] && /^data:application\/octet/.test(value['buffers'][0]['uri']))) {
238+
return false
239+
}
240+
241+
return true
242+
} catch (e) {
243+
console.warn(`Unable to decide on isJsonGltf ${e}`)
244+
return false
245+
}
246+
}

dashboard/src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { getPrefixByStoreUrl } from '@/utils/chain'
2424
import 'setimmediate';
2525
import i18n from './i18n'
2626
import mingo from 'mingo'
27+
import api from './fetch'
2728

2829
Vue.filter('shortAddress', shortAddress);
2930

@@ -33,6 +34,7 @@ Vue.filter('shortAddress', shortAddress);
3334
(window as any).R = getInstance;
3435
(window as any).W = web3FromAddress;
3536
(window as any).mingo = mingo;
37+
(window as any).api = api;
3638
// (window as any).migrateCollection = migrateCollection;
3739
// (window as any).migrateNFT = migrateNFT;
3840

dashboard/src/router/rmrk.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const rmrkFaq = () => import('@/components/rmrk/Faq.vue')
77
const Packs = () => import('@/components/rmrk/Pack/Packs.vue')
88
const PackItem = () => import('@/components/rmrk/Pack/PackItem.vue')
99
const CollectionItem = () => import('@/components/rmrk/Gallery/CollectionItem.vue')
10+
const ViewModel = () => import('@/components/rmrk/Gallery/ViewModel.vue')
1011

1112
export default [
1213
{
@@ -54,4 +55,9 @@ export default [
5455
name: 'collectionDetail',
5556
component: CollectionItem,
5657
},
58+
{
59+
path: '/rmrk/view',
60+
name: 'viewModel',
61+
component: ViewModel,
62+
},
5763
];

0 commit comments

Comments
 (0)