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

Commit 72d8b5e

Browse files
authored
Merge pull request #1535 from kkukelka/feat/nft-item-nav
1508 NFT item detail - enable navigation to other items in same collection
2 parents fa5cd58 + 922fde6 commit 72d8b5e

File tree

6 files changed

+154
-10
lines changed

6 files changed

+154
-10
lines changed

components/rmrk/Gallery/GalleryItem.vue

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
'is-12 is-theatre': viewMode === 'theatre',
3131
'is-6 is-offset-3': viewMode === 'default',
3232
}"
33+
@mouseenter="showNavigation = true"
34+
@mouseleave="showNavigation = false"
3335
>
3436
<div
3537
v-orientation="
@@ -63,6 +65,8 @@
6365
:mimeType="mimeType"
6466
/>
6567
</div>
68+
<Navigation v-if="nftsFromSameCollection && nftsFromSameCollection.length > 1" :showNavigation="showNavigation" :items="nftsFromSameCollection" :currentId="nft.id"/>
69+
6670
</div>
6771
<button
6872
id="fullscreen-view"
@@ -191,6 +195,7 @@ import { notificationTypes, showNotification } from '@/utils/notification';
191195
192196
import isShareMode from '@/utils/isShareMode';
193197
import nftById from '@/queries/nftById.graphql';
198+
import nftListIdsByCollection from '@/queries/nftListIdsByCollection.graphql';
194199
import { fetchNFTMetadata } from '../utils';
195200
import { get, set } from 'idb-keyval';
196201
import { MediaType } from '../types';
@@ -237,6 +242,7 @@ import Orientation from '@/directives/DeviceOrientation';
237242
History: () => import('@/components/rmrk/Gallery/History.vue'),
238243
Money: () => import('@/components/shared/format/Money.vue'),
239244
Name: () => import('@/components/rmrk/Gallery/Item/Name.vue'),
245+
Navigation: () => import('@/components/rmrk/Gallery/Item/Navigation.vue'),
240246
Sharing: () => import('@/components/rmrk/Gallery/Item/Sharing.vue'),
241247
Appreciation: () => import('./Appreciation.vue'),
242248
MediaResolver: () => import('../Media/MediaResolver.vue'),
@@ -255,6 +261,7 @@ export default class GalleryItem extends Vue {
255261
// private accountId: string = '';
256262
private passsword = '';
257263
private nft: NFT = emptyObject<NFT>();
264+
private nftsFromSameCollection: NFT[] = [];
258265
private imageVisible = true;
259266
private viewMode = 'default';
260267
private isFullScreenView = false;
@@ -264,6 +271,7 @@ export default class GalleryItem extends Vue {
264271
public emotes: Emote[] = [];
265272
public message = '';
266273
public priceChartData: [Date, number][][] = [];
274+
public showNavigation = false;
267275
268276
get accountId() {
269277
return this.$store.getters.getAuthAddress;
@@ -278,7 +286,7 @@ export default class GalleryItem extends Vue {
278286
279287
try {
280288
// const nft = await rmrkService.getNFT(this.id);
281-
this.$apollo.addSmartQuery('nft', {
289+
await this.$apollo.addSmartQuery('nft', {
282290
query: nftById,
283291
variables: {
284292
id: this.id,
@@ -287,7 +295,12 @@ export default class GalleryItem extends Vue {
287295
...nFTEntity,
288296
emotes: nFTEntity?.emotes?.nodes,
289297
}),
290-
result: () => this.fetchMetadata(),
298+
result: () => {
299+
Promise.all([
300+
this.fetchMetadata(),
301+
this.fetchCollectionItems()
302+
]);
303+
},
291304
pollInterval: 5000,
292305
});
293306
} catch (e) {
@@ -305,6 +318,36 @@ export default class GalleryItem extends Vue {
305318
this.priceChartData = data;
306319
}
307320
321+
public async fetchCollectionItems() {
322+
const collectionId = (this.nft as any)?.collectionId;
323+
if(collectionId) {
324+
// cancel request and get ids from store in case we already fetched collection data before
325+
if(this.$store.state.history?.currentCollection?.id === collectionId) {
326+
this.nftsFromSameCollection = this.$store.state.history.currentCollection?.nftIds || []
327+
return
328+
}
329+
try {
330+
const nfts = await this.$apollo.query({
331+
query: nftListIdsByCollection,
332+
variables: {
333+
id: collectionId,
334+
},
335+
})
336+
337+
const {
338+
data: {
339+
nFTEntities
340+
}
341+
} = nfts
342+
343+
this.nftsFromSameCollection = nFTEntities?.nodes.map((n: { id: string }) => n.id) || []
344+
this.$store.dispatch('history/setCurrentCollection', { id: collectionId, nftIds: this.nftsFromSameCollection })
345+
} catch (e) {
346+
showNotification(`${e}`, notificationTypes.warn);
347+
}
348+
}
349+
}
350+
308351
public async fetchMetadata() {
309352
if (this.nft['metadata'] && !this.meta['image']) {
310353
const m = await get(this.nft.metadata);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<template>
2+
<div class="navigation-container" :class="{ 'is-hidden-desktop': !showNavigation }">
3+
<b-button
4+
tag="nuxt-link"
5+
icon-left="chevron-left"
6+
:to="`/rmrk/gallery/${this.items[this.prevIndex]}`"
7+
outlined
8+
/>
9+
<b-button
10+
tag="nuxt-link"
11+
icon-left="chevron-right"
12+
:to="`/rmrk/gallery/${this.items[this.nextIndex]}`"
13+
outlined
14+
/>
15+
</div>
16+
</template>
17+
18+
<script lang="ts" >
19+
import { Component, Prop, Vue } from 'nuxt-property-decorator'
20+
21+
const components = {
22+
ProfileLink: () => import('@/components/rmrk/Profile/ProfileLink.vue')
23+
}
24+
25+
@Component({ components })
26+
export default class Navigation extends Vue {
27+
@Prop({type: Array}) readonly items!: string[];
28+
@Prop(Boolean) public showNavigation !: boolean
29+
@Prop({type: String}) readonly currentId!: string;
30+
31+
get indexOfCurrentId(): number {
32+
return this.items.indexOf(this.currentId);
33+
}
34+
35+
get nextIndex(): number {
36+
// check if current item is last item of array
37+
return (this.indexOfCurrentId === this.items.length - 1) ? 0 : this.indexOfCurrentId + 1;
38+
}
39+
40+
get prevIndex(): number {
41+
// check if current item is first item of array
42+
return this.indexOfCurrentId === 0 ? this.items.length - 1 : this.indexOfCurrentId - 1;
43+
}
44+
45+
public mounted() {
46+
document.addEventListener('keyup', this.handleKeyEvent);
47+
48+
}
49+
50+
public beforeDestroy() {
51+
document.removeEventListener('keyup', this.handleKeyEvent);
52+
}
53+
54+
public handleKeyEvent(event) {
55+
return {
56+
'ArrowLeft': this.gotoNextItem(true),
57+
'ArrowRight': this.gotoNextItem(false),
58+
}[event.key]
59+
}
60+
61+
public gotoNextItem(reverse: boolean) {
62+
this.$router.push(`/rmrk/gallery/${this.items[reverse ? this.prevIndex : this.nextIndex]}`)
63+
}
64+
}
65+
</script>
66+
67+
<style scoped>
68+
.navigation-container {
69+
position: absolute;
70+
top: 0px;
71+
right: 0px;
72+
bottom: 0px;
73+
left: 0px;
74+
z-index: 999;
75+
display: flex;
76+
align-items: center;
77+
justify-content: space-between;
78+
}
79+
</style>

components/rmrk/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,4 @@ export const getRandomIntInRange = (min: number, max: number): number => {
354354
min = Math.ceil(min)
355355
max = Math.floor(max)
356356
return Math.floor(Math.random() * (max - min + 1)) + min
357-
}
357+
}

icons.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
faTrash, faCloudDownloadAlt, faKey,
77
faExternalLinkAlt, faUpload, faUsers,
88
faQuestionCircle, faMinus, faSearch,
9-
faInfoCircle, faChevronDown, faChevronUp, faChevronLeft,
9+
faInfoCircle, faChevronDown, faChevronUp, faChevronLeft, faChevronRight,
1010
faHeart, faCaretDown, faCaretUp, faInfo,
1111
faShareSquare, faCopy, faBookmark,
1212
faLink, faLanguage,
@@ -44,9 +44,9 @@ library.add(
4444
faKey, faExternalLinkAlt, faUpload,
4545
faUsers, faQuestionCircle, faMinus,
4646
faSearch, faInfoCircle, faChevronDown,
47-
faChevronUp, faChevronLeft, faHeart,
47+
faChevronUp, faChevronLeft, faChevronRight,
4848
faCaretDown, faCaretUp, faInfo, faShareSquare,
49-
faBookmark, faLink,
49+
faBookmark, faLink, faHeart,
5050
faLanguage, faQuestion, faEye,
5151
faPrint, faCommentAlt, faMagic,
5252
faLeaf, faFlask,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
query nftListByCollection($id: String!) {
2+
nFTEntities(
3+
filter: {
4+
collectionId: { equalTo: $id }
5+
burned: { distinctFrom: true }
6+
}
7+
) {
8+
nodes {
9+
id
10+
}
11+
}
12+
}

store/history.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ export interface HistoryItem {
1010
}
1111

1212
export const state = () => ({
13-
visitedNFTs: []
13+
visitedNFTs: [],
14+
currentCollection: {
15+
id: '',
16+
nftIds: []
17+
}
1418
})
1519

1620
export type HistoryState = ReturnType<typeof state>
@@ -24,7 +28,7 @@ export const getters: GetterTree<HistoryState, HistoryState> = {
2428
};
2529

2630
export const mutations: MutationTree<HistoryState> = {
27-
updateHistoryItems(state: any, data: HistoryItem | string) {
31+
UPDATE_HISTORY_ITEM(state: any, data: HistoryItem | string) {
2832
if(typeof data === 'string') {
2933
state.visitedNFTs = state.visitedNFTs.filter(item => item.id !== data);
3034
}
@@ -39,14 +43,20 @@ export const mutations: MutationTree<HistoryState> = {
3943
}
4044
state.visitedNFTs.unshift(data);
4145
}
46+
},
47+
UPDATE_CURRENT_COLLECTION(state: any, data: {id: string, nftIds: string[]}) {
48+
state.currentCollection = data;
4249
}
4350
};
4451

4552
export const actions: ActionTree<HistoryState, HistoryState> = {
4653
addHistoryItem({ commit }: { commit: Commit }, data: HistoryItem) {
47-
commit('updateHistoryItems', data);
54+
commit('UPDATE_HISTORY_ITEM', data);
4855
},
4956
removeHistoryItem({ commit }: { commit: Commit }, data: string) {
50-
commit('updateHistoryItems', data);
57+
commit('UPDATE_HISTORY_ITEM', data);
58+
},
59+
setCurrentCollection({ commit }: { commit: Commit }, data: {id: string, nftIds: string[]}) {
60+
commit('UPDATE_CURRENT_COLLECTION', data);
5161
},
5262
};

0 commit comments

Comments
 (0)