Skip to content

Commit

Permalink
feat: List NFT collections from account details. (#721)
Browse files Browse the repository at this point in the history
Signed-off-by: Tim Schmidt <tim@launchbadge.com>
Signed-off-by: Eric Le Ponner <eric.leponner@icloud.com>
Signed-off-by: Simon Viénot <simon.vienot@icloud.com>
Co-authored-by: Eric Le Ponner <eric.leponner@icloud.com>
Co-authored-by: Simon Viénot <simon.vienot@icloud.com>
  • Loading branch information
3 people authored Jan 24, 2024
1 parent c597486 commit e32fc94
Show file tree
Hide file tree
Showing 15 changed files with 701 additions and 41 deletions.
2 changes: 1 addition & 1 deletion src/components/account/BalanceTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
/>
</o-table-column>

<o-table-column v-slot="props" field="balance" label="Balance" position="right">
<o-table-column v-slot="props" field="balance" label="Balance/Nb of NFTs" position="right">
<TokenAmount v-bind:amount="BigInt(props.row.balance)"
v-bind:token-id="props.row.token_id"/>
</o-table-column>
Expand Down
130 changes: 130 additions & 0 deletions src/components/account/CollectionTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<!--
-
- Hedera Mirror Node Explorer
-
- Copyright (C) 2021 - 2023 Hedera Hashgraph, LLC
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-->

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- TEMPLATE -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<template>
<o-table
:data="collection"
:loading="loading"
:hoverable="true"
:paginated="!isTouchDevice"
backend-pagination
:total="totalRowCount"
:current-page="currentPage"
:per-page="pageSize"
@page-change="onPageChange"
:striped="true"
:v-model:current-page="currentPage"
:mobile-breakpoint="ORUGA_MOBILE_BREAKPOINT"
aria-current-label="Current page"
aria-next-label="Next page"
aria-page-label="Page"
aria-previous-label="Previous page"
@cell-click="handleClick"
>
<o-table-column v-slot="props" field="serial" label="Serial">
{{ props.row.serial_number }}
</o-table-column>
</o-table>

<EmptyTable v-if="!collection.length"/>

</template>

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- SCRIPT -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<script lang="ts">
import {ComputedRef, defineComponent, inject, PropType, Ref} from 'vue';
import {Nft} from "@/schemas/HederaSchemas";
import TokenLink from "@/components/values/TokenLink.vue";
import {ORUGA_MOBILE_BREAKPOINT} from '@/App.vue';
import EmptyTable from "@/components/EmptyTable.vue";
import {routeManager} from "@/router";
import {CollectionTableController} from "@/components/account/CollectionTableController";
export default defineComponent({
name: 'CollectionTable',
components: {
EmptyTable,
TokenLink,
},
props: {
controller: {
type: Object as PropType<CollectionTableController>,
required: true
},
tokenId: {
type: String,
required: true
}
},
setup(props) {
const isTouchDevice = inject('isTouchDevice', false)
const isMediumScreen = inject('isMediumScreen', true)
const handleClick = (
n: Nft,
c: unknown,
i: number,
ci: number,
event: MouseEvent,
) => {
if (n.token_id && n.serial_number) {
routeManager.routeToSerial(
n.token_id,
n.serial_number,
event.ctrlKey || event.metaKey,
);
}
};
return {
isTouchDevice,
isMediumScreen,
collection: props.controller.rows as ComputedRef<Nft[]>,
loading: props.controller.loading as ComputedRef<boolean>,
totalRowCount: props.controller.totalRowCount as ComputedRef<number>,
currentPage: props.controller.currentPage as Ref<number>,
onPageChange: props.controller.onPageChange,
pageSize: props.controller.pageSize as Ref<Number>,
handleClick,
ORUGA_MOBILE_BREAKPOINT
}
}
});
</script>

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- STYLE -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<style scoped>
</style>
86 changes: 86 additions & 0 deletions src/components/account/CollectionTableController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*-
*
* Hedera Mirror Node Explorer
*
* Copyright (C) 2021 - 2023 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import {Nft, Nfts} from "@/schemas/HederaSchemas";
import {ComputedRef, Ref} from "vue";
import axios from "axios";
import {KeyOperator, SortOrder, TableController} from "@/utils/table/TableController";
import {Router} from "vue-router";

export class CollectionTableController extends TableController<Nft, number> {

public readonly accountId: Ref<string | null>
public readonly tokenId: string

//
// Public
//

public constructor(router: Router, tokenId: string, accountId: Ref<string | null>, pageSize: ComputedRef<number>) {
super(router, pageSize, 10 * pageSize.value, 5000, 10, 100)
this.accountId = accountId
this.tokenId = tokenId
}

//
// TableController
//

public async load(serialNumber: number | null, operator: KeyOperator, order: SortOrder, limit: number): Promise<Nft[] | null> {
if (this.tokenId == null || this.accountId.value == null) {
return Promise.resolve(null)
}

const params = {} as {
limit: number
"token.id": string | undefined
serialnumber: string | undefined
order: string
}
params.limit = limit
params.order = order
params["token.id"] = this.tokenId

if (serialNumber !== null) {
params.serialnumber = operator + ":" + serialNumber
}

const { data } = await axios.get<Nfts>(
`api/v1/accounts/${this.accountId.value}/nfts`,
{params: params},
)

const nfts = data.nfts ?? null

return Promise.resolve(nfts)
}

public keyFor(row: Nft): number {
return row.serial_number ?? 0
}

public stringFromKey(serialNumber: number): string {
return serialNumber.toString()
}

public keyFromString(s: string): number | null {
return Number(s)
}
}
119 changes: 119 additions & 0 deletions src/components/account/NftsTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<!--
-
- Hedera Mirror Node Explorer
-
- Copyright (C) 2021 - 2023 Hedera Hashgraph, LLC
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-->

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- TEMPLATE -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<template>
<o-table
:data="collections"
:paginated="!isTouchDevice"
:per-page="perPage"
@cell-click="handleClick"

:hoverable="true"
:narrowed="true"
:striped="true"
:mobile-breakpoint="ORUGA_MOBILE_BREAKPOINT"

aria-current-label="Current page"
aria-next-label="Next page"
aria-page-label="Page"
aria-previous-label="Previous page"
>
<o-table-column v-slot="props" field="token_id" label="Token">
<TokenLink
v-bind:show-extra="true"
v-bind:token-id="props.row.tokenId"
v-bind:no-anchor="true"
/>
</o-table-column>
<o-table-column v-slot="props" field="owned" label="Owned" position="right">
{{ props.row.collectionSize }}
</o-table-column>
</o-table>

<EmptyTable v-if="!collections.length"/>

</template>

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- SCRIPT -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<script lang="ts">
import {computed, defineComponent, inject, PropType} from 'vue';
import TokenLink from "@/components/values/TokenLink.vue";
import {ORUGA_MOBILE_BREAKPOINT} from '@/App.vue';
import EmptyTable from "@/components/EmptyTable.vue";
import {routeManager} from "@/router";
import {useRoute} from "vue-router";
import {NftCollectionInfo} from "@/utils/cache/NftCollectionCache";
export default defineComponent({
name: 'NftsTable',
components: {
EmptyTable,
TokenLink,
},
props: {
collections: {
type: Object as PropType<NftCollectionInfo[]>,
required: true
},
},
setup() {
const route = useRoute();
const isTouchDevice = inject('isTouchDevice', false)
const isMediumScreen = inject('isMediumScreen', true)
const perPage = computed(() => isMediumScreen ? 15 : 5)
const handleClick = (nft: NftCollectionInfo, c: unknown, i: number, ci: number, event: MouseEvent) => {
if (nft.tokenId) {
routeManager.routeToCollection(route.params.accountId as string, nft.tokenId, event.ctrlKey || event.metaKey)
}
}
return {
isTouchDevice,
isMediumScreen,
perPage,
handleClick,
ORUGA_MOBILE_BREAKPOINT
}
}
});
</script>

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- STYLE -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<style scoped>
</style>
2 changes: 1 addition & 1 deletion src/components/allowances/ApproveAllowanceDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export default defineComponent({
result ||= allowanceChoice.value !== "token"
result ||= selectedSpender.value !== props.currentTokenAllowance?.spender
result ||= selectedToken.value !== props.currentTokenAllowance.token_id
result ||= selectedTokenAmount.value !== props.currentTokenAllowance?.amount_granted.toString() ?? null
result ||= selectedTokenAmount.value !== (props.currentTokenAllowance?.amount_granted.toString() ?? null)
} else {
result = true
}
Expand Down
Loading

0 comments on commit e32fc94

Please sign in to comment.