Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet-dashboard): add grid for visual assets #3758

Merged
merged 21 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b0c9983
feat(wallet-dashboard): add protected layout
VmMad Oct 25, 2024
670f496
feat: add missing TopNav
VmMad Oct 28, 2024
d43a921
fix: tests
VmMad Oct 28, 2024
5ba9405
Merge branch 'develop' into tooling-dashboard/add-connected-layout
VmMad Oct 28, 2024
1100c2c
fix: imports
VmMad Oct 28, 2024
0f30b19
Merge branch 'develop' into tooling-dashboard/add-connected-layout
evavirseda Oct 28, 2024
a74ff0e
feat(wallet-dashboard): add assets page
VmMad Oct 28, 2024
9d4abe6
Merge branch 'tooling-dashboard/add-connected-layout' into tooling-da…
VmMad Oct 28, 2024
37ce97e
fix: add missing themes
VmMad Oct 28, 2024
8a8f711
Merge branch 'tooling-dashboard/add-connected-layout' into tooling-da…
VmMad Oct 28, 2024
6de4fc2
feat: improve connect button size
VmMad Oct 28, 2024
bc4612b
revert: "feat: improve connect button size"
VmMad Oct 28, 2024
516126f
Merge branch 'develop' into tooling-dashboard/add-assets-layout
VmMad Oct 29, 2024
551b48f
feat(wallet-dashboard): add grid for visual assets
VmMad Oct 29, 2024
ea32c22
refactor: remove unnecessary useMemo
VmMad Oct 30, 2024
8b5348a
fix: remove commented lines
VmMad Oct 30, 2024
f6ddc10
Merge branch 'develop' into tooling-dashboard/add-assets-layout
evavirseda Oct 30, 2024
a4db526
Merge branch 'tooling-dashboard/add-assets-layout' into tooling-dashb…
VmMad Nov 4, 2024
0753921
fix: improve type
VmMad Nov 6, 2024
f0131a0
Merge branch 'develop' into tooling-dashboard/add-assets-styles
VmMad Nov 6, 2024
9c5032e
fix: update IotaLogo
VmMad Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ export * from './useUnlockTimelockedObjectsTransaction';
export * from './useGetAllOwnedObjects';
export * from './useGetTimelockedStakedObjects';
export * from './useGetActiveValidatorsInfo';
export * from './useCursorPagination';

export * from './stake';
56 changes: 56 additions & 0 deletions apps/core/src/hooks/useCursorPagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import { type InfiniteData, type UseInfiniteQueryResult } from '@tanstack/react-query';

interface PaginationProps {
hasFirst: boolean;
hasPrev: boolean;
hasNext: boolean;
onFirst(): void;
onPrev(): void;
onNext(): void;
}

interface CursorPaginationProps extends PaginationProps {
currentPage: number;
}

export interface PaginationResponse<Cursor = string> {
nextCursor: Cursor | null;
hasNextPage: boolean;
}

export function useCursorPagination<T>(query: UseInfiniteQueryResult<InfiniteData<T>>) {
const [currentPage, setCurrentPage] = useState(0);

return {
...query,
data: query.data?.pages[currentPage],
pagination: {
onFirst: () => setCurrentPage(0),
onNext: () => {
if (!query.data || query.isFetchingNextPage) {
return;
}

// Make sure we are at the end before fetching another page
if (currentPage >= query.data.pages.length - 1) {
query.fetchNextPage();
}

setCurrentPage(currentPage + 1);
},
onPrev: () => {
setCurrentPage(Math.max(currentPage - 1, 0));
},
hasFirst: currentPage !== 0,
hasNext:
!query.isFetchingNextPage &&
(currentPage < (query.data?.pages.length ?? 0) - 1 || !!query.hasNextPage),
hasPrev: currentPage !== 0,
currentPage,
} satisfies CursorPaginationProps,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

import { InfoBox, InfoBoxStyle, InfoBoxType, Select, SelectSize } from '@iota/apps-ui-kit';
import { useIotaClientQuery, useIotaClient, useIotaClientInfiniteQuery } from '@iota/dapp-kit';
import { useCursorPagination } from '@iota/core';
import { Warning } from '@iota/ui-icons';
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { PAGE_SIZES_RANGE_20_60 } from '~/lib/constants';
import { generateEpochsTableColumns } from '~/lib/ui';
import { numberSuffix } from '~/lib/utils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import { useIotaClient } from '@iota/dapp-kit';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useRef, useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { useCursorPagination } from '@iota/core';
import {
DEFAULT_TRANSACTIONS_LIMIT,
useGetTransactionBlocks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { InfoBox, InfoBoxStyle, InfoBoxType, Select, SelectSize } from '@iota/ap
import { useIotaClientQuery } from '@iota/dapp-kit';
import { Warning } from '@iota/ui-icons';
import { useMemo, useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { DEFAULT_CHECKPOINTS_LIMIT, useGetCheckpoints } from '~/hooks/useGetCheckpoints';
import { PAGE_SIZES_RANGE_20_60 } from '~/lib/constants';
import { generateCheckpointsTableColumns } from '~/lib/ui';
import { numberSuffix } from '~/lib/utils';
import { useCursorPagination } from '@iota/core';

interface CheckpointsTableProps {
disablePagination?: boolean;
Expand Down
9 changes: 7 additions & 2 deletions apps/explorer/src/components/owned-objects/OwnedObjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useGetKioskContents, useGetOwnedObjects, useLocalStorage } from '@iota/core';
import {
useGetKioskContents,
useGetOwnedObjects,
useLocalStorage,
useCursorPagination,
} from '@iota/core';
import {
Button,
ButtonSize,
Expand All @@ -27,7 +32,7 @@ import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react';
import { ListView, SmallThumbnailsView, ThumbnailsView } from '~/components';
import { ObjectViewMode } from '~/lib/enums';
import { Pagination, useCursorPagination } from '~/components/ui';
import { Pagination } from '~/components/ui';
import { PAGE_SIZES_RANGE_10_50 } from '~/lib/constants';

const SHOW_PAGINATION_MAX_ITEMS = 9;
Expand Down
40 changes: 1 addition & 39 deletions apps/explorer/src/components/ui/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import { Button, ButtonSize, ButtonType } from '@iota/apps-ui-kit';
import { ArrowLeft, ArrowRight, DoubleArrowLeft } from '@iota/ui-icons';
import { type InfiniteData, type UseInfiniteQueryResult } from '@tanstack/react-query';
import { useState } from 'react';

interface PaginationProps {
Expand All @@ -16,49 +15,12 @@ interface PaginationProps {
onNext(): void;
}

interface CursorPaginationProps extends PaginationProps {
currentPage: number;
}

export interface PaginationResponse<Cursor = string> {
nextCursor: Cursor | null;
hasNextPage: boolean;
}

export function useCursorPagination<T>(query: UseInfiniteQueryResult<InfiniteData<T>>) {
const [currentPage, setCurrentPage] = useState(0);

return {
...query,
data: query.data?.pages[currentPage],
pagination: {
onFirst: () => setCurrentPage(0),
onNext: () => {
if (!query.data || query.isFetchingNextPage) {
return;
}

// Make sure we are at the end before fetching another page
if (currentPage >= query.data.pages.length - 1) {
query.fetchNextPage();
}

setCurrentPage(currentPage + 1);
},
onPrev: () => {
setCurrentPage(Math.max(currentPage - 1, 0));
},
hasFirst: currentPage !== 0,
hasNext:
!query.isFetchingNextPage &&
(currentPage < (query.data?.pages.length ?? 0) - 1 || !!query.hasNextPage),
hasPrev: currentPage !== 0,
currentPage,
} satisfies CursorPaginationProps,
};
}

/** @deprecated Prefer `useCursorPagination` + `useInfiniteQuery` for pagination. */
/** @deprecated Prefer `useCursorPagination` from core + `useInfiniteQuery` for pagination. */
export function usePaginationStack<Cursor = string>() {
const [stack, setStack] = useState<Cursor[]>([]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import { DropdownPosition, Select, SelectSize } from '@iota/apps-ui-kit';
import { useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { useCursorPagination } from '@iota/core';
import {
DEFAULT_TRANSACTIONS_LIMIT,
useGetTransactionBlocks,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export { VisualAssetCard } from './VisualAssetCard';
export * from './VisualAssetCard';
export * from './visual-asset-card.enums';
68 changes: 42 additions & 26 deletions apps/wallet-dashboard/app/(protected)/assets/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@

'use client';

import { AssetCard, VirtualList } from '@/components';
import { AssetCard, PageSizeSelector, PaginationOptions } from '@/components';
import { ASSETS_ROUTE } from '@/lib/constants/routes.constants';
import { Panel, Title, Chip } from '@iota/apps-ui-kit';
import { hasDisplayData, useGetOwnedObjects } from '@iota/core';
import { Panel, Title, Chip, TitleSize, DropdownPosition } from '@iota/apps-ui-kit';
import { hasDisplayData, useCursorPagination, useGetOwnedObjects } from '@iota/core';
import { useCurrentAccount } from '@iota/dapp-kit';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { useState } from 'react';
import { AssetCategory } from '@/lib/enums';
import Link from 'next/link';
import { VisibilityOff } from '@iota/ui-icons';
import { useRouter } from 'next/navigation';

const PAGINATION_RANGE = [20, 40, 60];

const ASSET_CATEGORIES: { label: string; value: AssetCategory }[] = [
{
Expand All @@ -26,22 +29,22 @@ const ASSET_CATEGORIES: { label: string; value: AssetCategory }[] = [

export default function AssetsDashboardPage(): React.JSX.Element {
const [selectedCategory, setSelectedCategory] = useState<AssetCategory>(AssetCategory.Visual);
const [limit, setLimit] = useState<number>(PAGINATION_RANGE[1]);
const router = useRouter();

const account = useCurrentAccount();
const {
data: ownedObjects,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useGetOwnedObjects(account?.address);
const ownedObjectsQuery = useGetOwnedObjects(account?.address, undefined, limit);

const { data, pagination } = useCursorPagination(ownedObjectsQuery);
evavirseda marked this conversation as resolved.
Show resolved Hide resolved

const { data: ownedObjects } = data || {};

const [visual, nonVisual] = (() => {
const visual: IotaObjectData[] = [];
const nonVisual: IotaObjectData[] = [];

ownedObjects?.pages
.flatMap((page) => page.data)
.filter((asset) => asset.data && asset.data.objectId)
ownedObjects
?.filter((asset) => asset.data && asset.data.objectId)
.forEach((asset) => {
if (asset.data) {
if (hasDisplayData(asset)) {
Expand All @@ -64,7 +67,7 @@ export default function AssetsDashboardPage(): React.JSX.Element {

return (
<Panel>
<Title title="Assets" />
<Title title="Assets" size={TitleSize.Medium} />
<div className="px-lg">
<div className="flex flex-row items-center justify-start gap-xs py-xs">
{ASSET_CATEGORIES.map((tab) => (
Expand All @@ -77,18 +80,31 @@ export default function AssetsDashboardPage(): React.JSX.Element {
))}
</div>

<div className="max-h-[600px] overflow-auto py-sm">
<VirtualList
items={assetList}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
fetchNextPage={fetchNextPage}
estimateSize={() => 180}
render={(asset) => (
<Link href={ASSETS_ROUTE.path + `/${asset.objectId}`}>
<AssetCard asset={asset} />
</Link>
)}
<div className="grid-template-visual-assets grid max-h-[600px] gap-md overflow-auto py-sm">
{assetList.map((asset) => (
<div key={asset.digest}>
<AssetCard
asset={asset}
icon={<VisibilityOff />}
onClick={() =>
router.push(ASSETS_ROUTE.path + `/${asset.objectId}`)
}
/>
</div>
))}
</div>
<div className="flex flex-row items-center justify-end py-xs">
<PaginationOptions
pagination={pagination}
action={
<PageSizeSelector
pagination={pagination}
range={PAGINATION_RANGE}
dropdownPosition={DropdownPosition.Top}
setLimit={(e) => setLimit(e)}
limit={limit.toString()}
/>
}
/>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions apps/wallet-dashboard/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,8 @@ body {
'coins activity activity';
}
}

.grid-template-visual-assets {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
}
47 changes: 23 additions & 24 deletions apps/wallet-dashboard/components/Cards/AssetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,37 @@

import { IotaObjectData } from '@iota/iota-sdk/client';
import React from 'react';
import { Box, ExternalImage } from '@/components/index';
import { useGetNFTMeta } from '@iota/core';
import { FlexDirection } from '@/lib/ui/enums';
import { VisualAssetCard, VisualAssetType, type VisualAssetCardProps } from '@iota/apps-ui-kit';

interface AssetCardProps {
interface AssetCardProps extends Pick<VisualAssetCardProps, 'onClick' | 'onIconClick' | 'icon'> {
asset: IotaObjectData;
flexDirection?: FlexDirection;
}

function AssetCard({ asset, flexDirection }: AssetCardProps): React.JSX.Element {
export function AssetCard({
asset,
onClick,
onIconClick,
icon,
}: AssetCardProps): React.JSX.Element | null {
const { data: nftMeta } = useGetNFTMeta(asset.objectId);

if (!asset.display || !nftMeta || !nftMeta.imageUrl) {
return null;
}

return (
<Box>
<div className={`flex ${flexDirection} w-full gap-2`}>
{asset.display && nftMeta && nftMeta.imageUrl && (
<ExternalImage
src={nftMeta.imageUrl}
alt={nftMeta.name ?? asset.display.data?.name}
width={80}
height={80}
className="object-cover"
/>
)}
<div>
<p>Digest: {asset.digest}</p>
<p>Object ID: {asset.objectId}</p>
{asset.type ? <p>Type: {asset.type}</p> : null}
<p>Version: {asset.version}</p>
</div>
</div>
</Box>
<VisualAssetCard
assetSrc={nftMeta?.imageUrl ?? asset?.display?.data?.imageUrl ?? ''}
assetTitle={nftMeta?.name ?? asset?.display?.data?.name}
assetType={VisualAssetType.Image}
altText={nftMeta?.name ?? (asset?.display?.data?.name || 'NFT')}
isHoverable
icon={icon}
onClick={onClick}
onIconClick={onIconClick}
/>
);
}

export default AssetCard;
2 changes: 1 addition & 1 deletion apps/wallet-dashboard/components/Cards/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
// SPDX-License-Identifier: Apache-2.0

export { default as StakeCard } from './StakeCard';
export { default as AssetCard } from './AssetCard';
export * from './AssetCard';
Loading
Loading